diff --git a/api/src/opentrons/hardware_control/dev_types.py b/api/src/opentrons/hardware_control/dev_types.py index e6122bf86aa..e2b8e542037 100644 --- a/api/src/opentrons/hardware_control/dev_types.py +++ b/api/src/opentrons/hardware_control/dev_types.py @@ -99,6 +99,7 @@ class PipetteDict(InstrumentDict): supported_tips: Dict[PipetteTipType, SupportedTipsDefinition] pipette_bounding_box_offsets: PipetteBoundingBoxOffsetDefinition current_nozzle_map: NozzleMap + lld_settings: Optional[Dict[str, Dict[str, float]]] class PipetteStateDict(TypedDict): diff --git a/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py b/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py index 0e9e8690b64..8c333d990fd 100644 --- a/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py +++ b/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py @@ -220,6 +220,7 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict: "default_dispense_flow_rates", "back_compat_names", "supported_tips", + "lld_settings", ] instr_dict = instr.as_dict() @@ -259,6 +260,7 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict: result[ "pipette_bounding_box_offsets" ] = instr.config.pipette_bounding_box_offsets + result["lld_settings"] = instr.config.lld_settings return cast(PipetteDict, result) @property diff --git a/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py b/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py index db02775e3d0..94c5ce8b736 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py @@ -236,6 +236,7 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict: "default_dispense_flow_rates", "back_compat_names", "supported_tips", + "lld_settings", ] instr_dict = instr.as_dict() @@ -280,6 +281,7 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict: result[ "pipette_bounding_box_offsets" ] = instr.config.pipette_bounding_box_offsets + result["lld_settings"] = instr.config.lld_settings return cast(PipetteDict, result) @property diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index 35b3494b5f2..6b040243193 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -496,7 +496,10 @@ def _get_module_core( ) def load_instrument( - self, instrument_name: PipetteNameType, mount: Mount + self, + instrument_name: PipetteNameType, + mount: Mount, + liquid_presence_detection: bool = False, ) -> InstrumentCore: """Load an instrument into the protocol. @@ -515,6 +518,7 @@ def load_instrument( tipOverlapNotAfterVersion=overlap_versions.overlap_for_api_version( self._api_version ), + liquidPresenceDetection=liquid_presence_detection, ) ) 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 02fc2003733..9e8912c5629 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 @@ -346,7 +346,10 @@ def load_module( return module_core def load_instrument( - self, instrument_name: PipetteNameType, mount: Mount + self, + instrument_name: PipetteNameType, + mount: Mount, + liquid_presence_detection: bool = False, ) -> LegacyInstrumentCore: """Load an instrument.""" attached = { diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py index 9fb9d1a0f51..002ca5f6017 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py @@ -29,7 +29,10 @@ class LegacyProtocolCoreSimulator( _instruments: Dict[Mount, Optional[LegacyInstrumentCoreSimulator]] # type: ignore[assignment] def load_instrument( # type: ignore[override] - self, instrument_name: PipetteNameType, mount: Mount + self, + instrument_name: PipetteNameType, + mount: Mount, + liquid_presence_detection: bool = False, ) -> LegacyInstrumentCoreSimulator: """Create a simulating instrument context.""" pipette_generation = convert_to_pipette_name_type( diff --git a/api/src/opentrons/protocol_api/core/protocol.py b/api/src/opentrons/protocol_api/core/protocol.py index a554c14e306..3b7aa87a5bb 100644 --- a/api/src/opentrons/protocol_api/core/protocol.py +++ b/api/src/opentrons/protocol_api/core/protocol.py @@ -124,7 +124,10 @@ def load_module( @abstractmethod def load_instrument( - self, instrument_name: PipetteNameType, mount: Mount + self, + instrument_name: PipetteNameType, + mount: Mount, + liquid_presence_detection: bool = False, ) -> InstrumentCoreType: ... diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index 1f724bd743b..82d5f73fbd6 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -38,6 +38,7 @@ AxisMaxSpeeds, requires_version, APIVersionError, + RobotTypeError, ) from ._types import OffDeckType @@ -870,6 +871,7 @@ def load_instrument( mount: Union[Mount, str, None] = None, tip_racks: Optional[List[Labware]] = None, replace: bool = False, + liquid_presence_detection: Optional[bool] = None, ) -> InstrumentContext: """Load a specific instrument for use in the protocol. @@ -897,6 +899,7 @@ def load_instrument( control ` applications. You cannot replace an instrument in the middle of a protocol being run from the Opentrons App or touchscreen. + :param bool liquid_presence_detection: If ``True``, enable liquid presence detection for instrument. Only available on Flex robots in API Version 2.20 and above. """ instrument_name = validation.ensure_lowercase_name(instrument_name) checked_instrument_name = validation.ensure_pipette_name(instrument_name) @@ -928,9 +931,24 @@ def load_instrument( f"Loading {checked_instrument_name} on {checked_mount.name.lower()} mount" ) + if ( + self._api_version < APIVersion(2, 20) + and liquid_presence_detection is not None + ): + raise APIVersionError( + "Liquid Presence Detection is only supported in API Version 2.20 and above." + ) + if ( + self._core.robot_type != "OT-3 Standard" + and liquid_presence_detection is not None + ): + raise RobotTypeError( + "Liquid presence detection only available on Flex robot." + ) instrument_core = self._core.load_instrument( instrument_name=checked_instrument_name, mount=checked_mount, + liquid_presence_detection=liquid_presence_detection or False, ) for tip_rack in tip_racks: diff --git a/api/src/opentrons/protocol_engine/commands/load_pipette.py b/api/src/opentrons/protocol_engine/commands/load_pipette.py index 5b3d1c22693..6e46e00d1d8 100644 --- a/api/src/opentrons/protocol_engine/commands/load_pipette.py +++ b/api/src/opentrons/protocol_engine/commands/load_pipette.py @@ -56,6 +56,10 @@ class LoadPipetteParams(BaseModel): "expressed as vN where N is an integer, counting up from v0. If None, the current " "highest version will be used.", ) + liquidPresenceDetection: Optional[bool] = Field( + None, + description="Enable liquid presence detection for this pipette. Defaults to False.", + ) class LoadPipetteResult(BaseModel): diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index 05a217b45ee..9a421c363e8 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -177,8 +177,11 @@ async def liquid_probe_in_place( ) well_def = self._state_view.labware.get_well_definition(labware_id, well_name) well_depth = well_def.depth + lld_min_height = self._state_view.pipettes.get_current_tip_lld_settings( + pipette_id=pipette_id + ) z_pos = await self._hardware_api.liquid_probe( - mount=hw_pipette.mount, max_z_dist=well_depth + mount=hw_pipette.mount, max_z_dist=well_depth - lld_min_height ) return float(z_pos) diff --git a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py index e5a13af3f9a..d453543266d 100644 --- a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py +++ b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py @@ -66,6 +66,7 @@ class LoadedStaticPipetteData: nozzle_map: NozzleMap back_left_corner_offset: Point front_right_corner_offset: Point + pipette_lld_settings: Optional[Dict[str, Dict[str, float]]] class VirtualPipetteDataProvider: @@ -275,6 +276,7 @@ def _get_virtual_pipette_static_config_by_model( # noqa: C901 front_right_corner_offset=Point( pip_front_right[0], pip_front_right[1], pip_front_right[2] ), + pipette_lld_settings=config.lld_settings, ) def get_virtual_pipette_static_config( @@ -321,6 +323,7 @@ def get_pipette_static_config( front_right_corner_offset=Point( front_right_offset[0], front_right_offset[1], front_right_offset[2] ), + pipette_lld_settings=pipette_dict["lld_settings"], ) diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index d1153e9a51e..748786d9bda 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -118,6 +118,7 @@ class StaticPipetteConfig: pipette_bounding_box_offsets: PipetteBoundingBoxOffsets bounding_nozzle_offsets: BoundingNozzlesOffsets default_nozzle_map: NozzleMap + lld_settings: Optional[Dict[str, Dict[str, float]]] @dataclass @@ -133,6 +134,7 @@ class PipetteState: static_config_by_id: Dict[str, StaticPipetteConfig] flow_rates_by_id: Dict[str, FlowRates] nozzle_configuration_by_id: Dict[str, Optional[NozzleMap]] + liquid_presence_detection_by_id: Dict[str, bool] class PipetteStore(HasState[PipetteState], HandlesActions): @@ -152,6 +154,7 @@ def __init__(self) -> None: static_config_by_id={}, flow_rates_by_id={}, nozzle_configuration_by_id={}, + liquid_presence_detection_by_id={}, ) def handle_action(self, action: Action) -> None: @@ -197,6 +200,7 @@ def _handle_command( # noqa: C901 front_right_offset=config.nozzle_map.front_right_nozzle_offset, ), default_nozzle_map=config.nozzle_map, + lld_settings=config.pipette_lld_settings, ) self._state.flow_rates_by_id[private_result.pipette_id] = config.flow_rates self._state.nozzle_configuration_by_id[ @@ -215,6 +219,9 @@ def _handle_command( # noqa: C901 pipetteName=command.params.pipetteName, mount=command.params.mount, ) + self._state.liquid_presence_detection_by_id[pipette_id] = ( + command.params.liquidPresenceDetection or False + ) self._state.aspirated_volume_by_id[pipette_id] = None self._state.movement_speed_by_id[pipette_id] = None self._state.attached_tip_by_id[pipette_id] = None @@ -618,6 +625,27 @@ def get_available_volume(self, pipette_id: str) -> Optional[float]: return max(0.0, working_volume - current_volume) if current_volume else None + def get_pipette_lld_settings( + self, pipette_id: str + ) -> Optional[Dict[str, Dict[str, float]]]: + """Get the liquid level settings for all possible tips for a single pipette.""" + return self.get_config(pipette_id).lld_settings + + def get_current_tip_lld_settings(self, pipette_id: str) -> float: + """Get the liquid level settings for pipette and its current tip.""" + attached_tip = self.get_attached_tip(pipette_id) + if attached_tip is None or attached_tip.volume is None: + return 0 + lld_settings = self.get_pipette_lld_settings(pipette_id) + tipVolume = str(attached_tip.volume) + if ( + lld_settings is None + or lld_settings[tipVolume] is None + or lld_settings[tipVolume]["minHeight"] is None + ): + return 0 + return float(lld_settings[tipVolume]["minHeight"]) + def validate_tip_state(self, pipette_id: str, expected_has_tip: bool) -> None: """Validate that a pipette's tip state matches expectations.""" attached_tip = self.get_attached_tip(pipette_id) @@ -801,3 +829,12 @@ def get_pipette_bounds_at_specified_move_to_position( pip_back_right_bound, pip_front_left_bound, ) + + def get_liquid_presence_detection(self, pipette_id: str) -> bool: + """Determine if liquid presence detection is enabled for this pipette.""" + try: + return self._state.liquid_presence_detection_by_id[pipette_id] + except KeyError as e: + raise errors.PipetteNotLoadedError( + f"Pipette {pipette_id} not found; unable to determine if pipette liquid presence detection enabled." + ) from e diff --git a/api/src/opentrons/protocol_reader/file_hasher.py b/api/src/opentrons/protocol_reader/file_hasher.py index ab7d42b980e..4852e6dfd45 100644 --- a/api/src/opentrons/protocol_reader/file_hasher.py +++ b/api/src/opentrons/protocol_reader/file_hasher.py @@ -7,6 +7,7 @@ from .file_reader_writer import BufferedFile +# TODO (spp: 2024-06-17): move file hasher to utils class FileHasher: """Hashing utility class that hashes a combination of protocol and labware files.""" diff --git a/api/src/opentrons/protocols/api_support/definitions.py b/api/src/opentrons/protocols/api_support/definitions.py index 483f95f4801..799af1993f3 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, 19) +MAX_SUPPORTED_VERSION = APIVersion(2, 20) """The maximum supported protocol API version in this release.""" MIN_SUPPORTED_VERSION = APIVersion(2, 0) diff --git a/api/src/opentrons/protocols/api_support/util.py b/api/src/opentrons/protocols/api_support/util.py index dbe9e16f7de..d5c988b153c 100644 --- a/api/src/opentrons/protocols/api_support/util.py +++ b/api/src/opentrons/protocols/api_support/util.py @@ -22,6 +22,9 @@ from opentrons.hardware_control.types import Axis from opentrons.hardware_control.util import ot2_axis_to_string from opentrons_shared_data.robot.dev_types import RobotType +from opentrons_shared_data.errors.exceptions import ( + UnsupportedHardwareCommand, +) if TYPE_CHECKING: from opentrons.protocol_api.labware import Well, Labware @@ -37,10 +40,14 @@ MODULE_LOG = logging.getLogger(__name__) +class RobotTypeError(UnsupportedHardwareCommand): + """Error raised when a protocol attempts to access behavior not available to the robot type in use.""" + + pass + + class APIVersionError(Exception): - """ - Error raised when a protocol attempts to access behavior not implemented - """ + """Error raised when a protocol attempts to access behavior not implemented in the API in use.""" pass @@ -48,6 +55,8 @@ class APIVersionError(Exception): class UnsupportedAPIError(Exception): """Error raised when a protocol attempts to use unsupported API.""" + pass + def _assert_gzero(val: Any, message: str) -> float: try: diff --git a/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py b/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py index 9a68084cc89..dc10b9bbad8 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py @@ -254,6 +254,7 @@ def test_load_instrument_pre_219( pipetteName=PipetteNameType.P300_SINGLE, mount=MountType.LEFT, tipOverlapNotAfterVersion="v0", + liquidPresenceDetection=False, ) ) ).then_return(commands.LoadPipetteResult(pipetteId="cool-pipette")) @@ -290,6 +291,7 @@ def test_load_instrument_post_219( pipetteName=PipetteNameType.P300_SINGLE, mount=MountType.LEFT, tipOverlapNotAfterVersion="v1", + liquidPresenceDetection=False, ) ) ).then_return(commands.LoadPipetteResult(pipetteId="cool-pipette")) diff --git a/api/tests/opentrons/protocol_api/test_protocol_context.py b/api/tests/opentrons/protocol_api/test_protocol_context.py index c792fc4574c..7f00bfb9ee8 100644 --- a/api/tests/opentrons/protocol_api/test_protocol_context.py +++ b/api/tests/opentrons/protocol_api/test_protocol_context.py @@ -14,7 +14,7 @@ from opentrons.hardware_control.modules.types import ModuleType, TemperatureModuleModel from opentrons.protocols.api_support import instrument as mock_instrument_support from opentrons.protocols.api_support.types import APIVersion -from opentrons.protocols.api_support.util import APIVersionError +from opentrons.protocols.api_support.util import APIVersionError, RobotTypeError from opentrons.protocol_api import ( MAX_SUPPORTED_VERSION, ProtocolContext, @@ -178,6 +178,64 @@ def test_deck(subject: ProtocolContext) -> None: assert isinstance(result, Deck) +@pytest.mark.parametrize("api_version", [APIVersion(2, 20)]) +def test_load_instrument_robot_type( + decoy: Decoy, + mock_core: ProtocolCore, + subject: ProtocolContext, +) -> None: + """Non-Flex robot type should raise a ValueError.""" + mock_tip_racks = [decoy.mock(cls=Labware), decoy.mock(cls=Labware)] + + decoy.when(mock_validation.ensure_lowercase_name("Gandalf")).then_return("gandalf") + decoy.when(mock_validation.ensure_pipette_name("gandalf")).then_return( + PipetteNameType.P300_SINGLE + ) + decoy.when( + mock_validation.ensure_mount_for_pipette( + "shadowfax", PipetteNameType.P300_SINGLE + ) + ).then_return(Mount.LEFT) + decoy.when(mock_core.robot_type).then_return("OT-2 Standard") + + with pytest.raises(RobotTypeError): + subject.load_instrument( + instrument_name="Gandalf", + mount="shadowfax", + tip_racks=mock_tip_racks, + liquid_presence_detection=False, + ) + + +@pytest.mark.parametrize("api_version", [APIVersion(2, 14)]) +def test_load_instrument_api_version( + decoy: Decoy, + mock_core: ProtocolCore, + subject: ProtocolContext, +) -> None: + """Using an API Version prior to 2.20 should raise a APIVersionError.""" + mock_tip_racks = [decoy.mock(cls=Labware), decoy.mock(cls=Labware)] + + decoy.when(mock_validation.ensure_lowercase_name("Gandalf")).then_return("gandalf") + decoy.when(mock_validation.ensure_pipette_name("gandalf")).then_return( + PipetteNameType.P300_SINGLE + ) + decoy.when( + mock_validation.ensure_mount_for_pipette( + "shadowfax", PipetteNameType.P300_SINGLE + ) + ).then_return(Mount.LEFT) + decoy.when(mock_core.robot_type).then_return("OT-3 Standard") + + with pytest.raises(APIVersionError): + subject.load_instrument( + instrument_name="Gandalf", + mount="shadowfax", + tip_racks=mock_tip_racks, + liquid_presence_detection=False, + ) + + def test_load_instrument( decoy: Decoy, mock_core: ProtocolCore, @@ -201,6 +259,7 @@ def test_load_instrument( mock_core.load_instrument( instrument_name=PipetteNameType.P300_SINGLE, mount=Mount.LEFT, + liquid_presence_detection=False, ) ).then_return(mock_instrument_core) @@ -253,6 +312,7 @@ def test_load_instrument_replace( mock_core.load_instrument( instrument_name=matchers.IsA(PipetteNameType), mount=matchers.IsA(Mount), + liquid_presence_detection=False, ) ).then_return(mock_instrument_core) decoy.when(mock_instrument_core.get_pipette_name()).then_return("Ada Lovelace") @@ -296,6 +356,7 @@ def test_96_channel_pipette_raises_if_another_pipette_attached( mock_core.load_instrument( instrument_name=PipetteNameType.P300_SINGLE, mount=Mount.RIGHT, + liquid_presence_detection=False, ) ).then_return(mock_instrument_core) diff --git a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py index 7a9aefd8e5d..218d299ee29 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py +++ b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py @@ -59,6 +59,7 @@ async def test_configure_for_volume_implementation( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI), back_left_corner_offset=Point(10, 20, 30), front_right_corner_offset=Point(40, 50, 60), + pipette_lld_settings={}, ) decoy.when( diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py index c93122efccc..e90e20586f1 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py @@ -63,6 +63,7 @@ async def test_load_pipette_implementation( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ) decoy.when( @@ -119,6 +120,7 @@ async def test_load_pipette_implementation_96_channel( nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ) decoy.when( diff --git a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py index c8db4b3191f..068ad6cfbd6 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py @@ -152,6 +152,7 @@ def loaded_static_pipette_data( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ) diff --git a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py index b77a8cf25a8..4b792376368 100644 --- a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py +++ b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py @@ -64,6 +64,7 @@ def test_get_virtual_pipette_static_config( nozzle_map=result.nozzle_map, back_left_corner_offset=Point(0, 0, 10.45), front_right_corner_offset=Point(0, 0, 10.45), + pipette_lld_settings={}, ) @@ -92,6 +93,7 @@ def test_configure_virtual_pipette_for_volume( nozzle_map=result1.nozzle_map, back_left_corner_offset=Point(-8.0, -22.0, -259.15), front_right_corner_offset=Point(-8.0, -22.0, -259.15), + pipette_lld_settings={"t50": {"minHeight": 0.5, "minVolume": 0.0}}, ) subject_instance.configure_virtual_pipette_for_volume( "my-pipette", 1, result1.model @@ -117,6 +119,7 @@ def test_configure_virtual_pipette_for_volume( nozzle_map=result2.nozzle_map, back_left_corner_offset=Point(-8.0, -22.0, -259.15), front_right_corner_offset=Point(-8.0, -22.0, -259.15), + pipette_lld_settings={"t50": {"minHeight": 0.5, "minVolume": 0.0}}, ) @@ -145,6 +148,7 @@ def test_load_virtual_pipette_by_model_string( nozzle_map=result.nozzle_map, back_left_corner_offset=Point(-16.0, 43.15, 35.52), front_right_corner_offset=Point(16.0, -43.15, 35.52), + pipette_lld_settings={}, ) @@ -237,6 +241,11 @@ def pipette_dict( backLeftCorner=[10, 20, 30], frontRightCorner=[40, 50, 60], ), + "lld_settings": { + "t50": {"minHeight": 0.5, "minVolume": 0}, + "t200": {"minHeight": 0.5, "minVolume": 0}, + "t1000": {"minHeight": 0.5, "minVolume": 0}, + }, } @@ -278,6 +287,11 @@ def test_get_pipette_static_config( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), back_left_corner_offset=Point(10, 20, 30), front_right_corner_offset=Point(40, 50, 60), + pipette_lld_settings={ + "t50": {"minHeight": 0.5, "minVolume": 0}, + "t200": {"minHeight": 0.5, "minVolume": 0}, + "t1000": {"minHeight": 0.5, "minVolume": 0}, + }, ) 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 82cf971595e..58a4a49940e 100644 --- a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py @@ -2078,6 +2078,7 @@ def test_get_next_drop_tip_location( back_left_corner=Point(x=10, y=20, z=30), front_right_corner=Point(x=40, y=50, z=60), ), + lld_settings={}, ) ) decoy.when(mock_pipette_view.get_mount("pip-123")).then_return(pipette_mount) diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py index d1b4ac29f7c..a99ac90e9e2 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -82,6 +82,7 @@ def test_sets_initial_state(subject: PipetteStore) -> None: static_config_by_id={}, flow_rates_by_id={}, nozzle_configuration_by_id={}, + liquid_presence_detection_by_id={}, ) @@ -748,6 +749,7 @@ def test_add_pipette_config( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -774,6 +776,7 @@ def test_add_pipette_config( back_left_corner=Point(x=1, y=2, z=3), front_right_corner=Point(x=4, y=5, z=6), ), + lld_settings={}, ) assert subject.state.flow_rates_by_id["pipette-id"].default_aspirate == {"a": 1.0} assert subject.state.flow_rates_by_id["pipette-id"].default_dispense == {"b": 2.0} diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_view.py b/api/tests/opentrons/protocol_engine/state/test_pipette_view.py index 8c27360ebad..e15c8401699 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_view.py @@ -62,6 +62,7 @@ def get_pipette_view( static_config_by_id: Optional[Dict[str, StaticPipetteConfig]] = None, flow_rates_by_id: Optional[Dict[str, FlowRates]] = None, nozzle_layout_by_id: Optional[Dict[str, Optional[NozzleMap]]] = None, + liquid_presence_detection_by_id: Optional[Dict[str, bool]] = None, ) -> PipetteView: """Get a pipette view test subject with the specified state.""" state = PipetteState( @@ -74,6 +75,7 @@ def get_pipette_view( static_config_by_id=static_config_by_id or {}, flow_rates_by_id=flow_rates_by_id or {}, nozzle_configuration_by_id=nozzle_layout_by_id or {}, + liquid_presence_detection_by_id=liquid_presence_detection_by_id or {}, ) return PipetteView(state=state) @@ -276,6 +278,7 @@ def test_get_pipette_working_volume( bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, ) }, ) @@ -306,6 +309,7 @@ def test_get_pipette_working_volume_raises_if_tip_volume_is_none( bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, ) }, ) @@ -345,6 +349,7 @@ def test_get_pipette_available_volume( bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, ), "pipette-id-none": StaticPipetteConfig( min_volume=1, @@ -360,6 +365,7 @@ def test_get_pipette_available_volume( bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, ), }, ) @@ -471,6 +477,7 @@ def test_get_static_config( bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, ) subject = get_pipette_view( @@ -521,6 +528,7 @@ def test_get_nominal_tip_overlap( bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, ) subject = get_pipette_view(static_config_by_id={"pipette-id": config}) @@ -781,6 +789,7 @@ def test_get_nozzle_bounds_at_location( default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, pipette_bounding_box_offsets=bounding_box_offsets, + lld_settings={}, ) }, ) diff --git a/api/tests/opentrons/protocol_engine/state/test_tip_state.py b/api/tests/opentrons/protocol_engine/state/test_tip_state.py index dfd693822c3..3572f81567f 100644 --- a/api/tests/opentrons/protocol_engine/state/test_tip_state.py +++ b/api/tests/opentrons/protocol_engine/state/test_tip_state.py @@ -148,6 +148,7 @@ def test_get_next_tip_returns_none( nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -208,6 +209,7 @@ def test_get_next_tip_returns_first_tip( nozzle_map=get_default_nozzle_map(pipette_name_type), back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -262,6 +264,7 @@ def test_get_next_tip_used_starting_tip( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -350,6 +353,7 @@ def test_get_next_tip_skips_picked_up_tip( nozzle_map=get_default_nozzle_map(pipette_name_type), back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -404,6 +408,7 @@ def test_get_next_tip_with_starting_tip( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -478,6 +483,7 @@ def test_get_next_tip_with_starting_tip_8_channel( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI_GEN2), back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -553,6 +559,7 @@ def test_get_next_tip_with_1_channel_followed_by_8_channel( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -584,6 +591,7 @@ def test_get_next_tip_with_1_channel_followed_by_8_channel( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI_GEN2), back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -659,6 +667,7 @@ def test_get_next_tip_with_starting_tip_out_of_tips( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -734,6 +743,7 @@ def test_get_next_tip_with_column_and_starting_tip( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_MULTI_GEN2), back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -786,6 +796,7 @@ def test_reset_tips( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ), ) @@ -838,6 +849,7 @@ def test_handle_pipette_config_action( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -921,6 +933,7 @@ def test_drop_tip( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -1047,6 +1060,7 @@ def test_active_channels( nozzle_map=nozzle_map, back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -1112,6 +1126,7 @@ def test_next_tip_uses_active_channels( nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -1197,6 +1212,7 @@ def test_next_tip_automatic_tip_tracking_with_partial_configurations( nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ), ) subject.handle_action( @@ -1355,6 +1371,7 @@ def test_next_tip_automatic_tip_tracking_tiprack_limits( nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, ), ) subject.handle_action( diff --git a/app-shell-odd/src/main.ts b/app-shell-odd/src/main.ts index 91e6c8d0d73..35fc0bae177 100644 --- a/app-shell-odd/src/main.ts +++ b/app-shell-odd/src/main.ts @@ -61,8 +61,8 @@ let rendererLogger: Logger app.prependOnceListener('ready', startUp) if (config.devtools) app.once('ready', installDevtools) -app.once('window-all-closed', () => { - log.debug('all windows closed, quitting the app') +function quitApplication(): void { + app.quit() closeBrokerConnection() .then(() => { app.quit() @@ -71,6 +71,16 @@ app.once('window-all-closed', () => { log.warn('Failed to properly close MQTT connections:', error) app.quit() }) +} + +app.once('window-all-closed', () => { + log.debug('all windows closed, quitting the app') + quitApplication() +}) + +app.once('render-process-gone', (_, __, details) => { + log.error('Renderer process has died, quitting the app', details) + quitApplication() }) function startUp(): void { diff --git a/app-testing/Makefile b/app-testing/Makefile index e1d9698d3cb..d295f917185 100644 --- a/app-testing/Makefile +++ b/app-testing/Makefile @@ -16,7 +16,7 @@ ruff-check: .PHONY: mypy mypy: - python -m pipenv run python -m mypy conftest.py automation tests citools + python -m pipenv run python -m mypy automation tests citools .PHONY: lint lint: black-check ruff-check mypy diff --git a/app-testing/automation/data/protocol.py b/app-testing/automation/data/protocol.py index 71c33ed0ce1..d48cdef9fb1 100644 --- a/app-testing/automation/data/protocol.py +++ b/app-testing/automation/data/protocol.py @@ -7,8 +7,6 @@ from pydantic import BaseModel, Field -from automation.resources.robot_data import module_types - GENERATED_PROTOCOLS_FOLDER = "generated_protocols" OVERRIDE_MONIKER = "_Override_" @@ -19,16 +17,7 @@ class Protocol(BaseModel): file_stem: str = Field(description="file name not including extension") file_extension: Literal["json", "py"] = Field(description="file extension of the protocol") robot: Literal["OT2", "Flex"] = Field(description="the robot type which will appear in the robot field in the app") - app_error: bool = Field(description="will analysis with the app raise an error") - robot_error: bool = Field(description="will analysis with the robot raise an error") - app_analysis_error: Optional[str] = Field(description="the exact error shown in the app popout", default=None) - robot_analysis_error: Optional[str] = Field(description="the exact analysis error from the robot", default=None) custom_labware: Optional[list[str]] = Field(description="list of custom labware file stems", default=None) - instruments: Optional[list[str]] = Field(description="list of instruments that will show in the app", default=None) - modules: Optional[list[module_types]] = Field(description="list of modules that will show in the app", default=None) - description: Optional[str] = Field(description="Details about this protocol", default=None) - expected_test_failure: bool = Field(description="Is this test expected to fail", default=False) - expected_test_reason: Optional[str] = Field(description="Reason test is failing", default=None) override_variable_name: Optional[str] = Field(description="The variable name to override", default=None) override_value: Optional[str] = Field(description="The value of the override", default=None) from_override: bool = Field(description="Is this protocol generated from an override", default=False) diff --git a/app-testing/automation/data/protocol_with_overrides.py b/app-testing/automation/data/protocol_with_overrides.py index f1410ac5f32..84a8d2261e0 100644 --- a/app-testing/automation/data/protocol_with_overrides.py +++ b/app-testing/automation/data/protocol_with_overrides.py @@ -39,16 +39,7 @@ def create_protocols(self) -> None: file_stem=new_file_stem, file_extension=self.file_extension, robot=self.robot, - app_error=self.app_error, - robot_error=self.robot_error, - app_analysis_error=self.app_analysis_error, - robot_analysis_error=self.robot_analysis_error, custom_labware=self.custom_labware, - instruments=self.instruments, - modules=self.modules, - description=self.description, - expected_test_failure=self.expected_test_failure, - expected_test_reason=self.expected_test_reason, from_override=True, override_value=override, ) diff --git a/app-testing/automation/data/protocols.py b/app-testing/automation/data/protocols.py index a1e8f3d5e24..26acbbbad03 100644 --- a/app-testing/automation/data/protocols.py +++ b/app-testing/automation/data/protocols.py @@ -17,102 +17,77 @@ class Protocols: file_stem="OT2_S_v6_P1000S_None_SimpleTransfer", file_extension="json", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v6_P20S_P300M_TransferReTransferLiquid: Protocol = Protocol( file_stem="OT2_S_v6_P20S_P300M_TransferReTransferLiquid", file_extension="json", robot="OT2", - app_error=False, - robot_error=False, ) OT2_X_v6_P20S_None_SimpleTransfer: Protocol = Protocol( file_stem="OT2_X_v6_P20S_None_SimpleTransfer", file_extension="json", robot="OT2", - app_error=True, - robot_error=True, - app_analysis_error="Cannot aspirate more than pipette max volume", - robot_analysis_error="?", ) OT2_S_v6_P300M_P20S_HS_Smoke620release: Protocol = Protocol( file_stem="OT2_S_v6_P300M_P20S_HS_Smoke620release", file_extension="json", robot="OT2", - app_error=False, - robot_error=False, + ) + + OT2_X_v6_P20S_P300M_HS_HSCollision: Protocol = Protocol( + file_stem="OT2_X_v6_P20S_P300M_HS_HSCollision", + file_extension="json", + robot="OT2", ) OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods: Protocol = Protocol( file_stem="OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods", file_extension="json", robot="OT2", - app_error=True, - robot_error=True, - app_analysis_error="Heater-Shaker cannot open its labware latch while it is shaking.", - robot_analysis_error="?", ) OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40: Protocol = Protocol( file_stem="OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40", file_extension="json", robot="OT2", - app_error=False, - robot_error=False, ) OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests: Protocol = Protocol( file_stem="OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests", file_extension="json", robot="OT2", - app_error=True, - robot_error=True, - app_analysis_error="Cannot aspirate more than pipette max volume", - robot_analysis_error="?", ) OT2_S_v6_P300M_P20S_MixTransferManyLiquids: Protocol = Protocol( file_stem="OT2_S_v6_P300M_P20S_MixTransferManyLiquids", file_extension="json", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer: Protocol = Protocol( file_stem="OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer", file_extension="json", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v3_P300SGen1_None_Gen1PipetteSimple: Protocol = Protocol( file_stem="OT2_S_v3_P300SGen1_None_Gen1PipetteSimple", file_extension="json", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps: Protocol = Protocol( file_stem="OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps", file_extension="json", robot="OT2", - app_error=False, - robot_error=False, ) Flex_X_v8_P1000_96_HS_GRIP_TC_TM_GripperCollisionWithTips: Protocol = Protocol( file_stem="Flex_X_v8_P1000_96_HS_GRIP_TC_TM_GripperCollisionWithTips", file_extension="json", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="Gripper collision with tips", ) ############################################################################################################ @@ -123,58 +98,41 @@ class Protocols: file_stem="OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError", file_extension="py", robot="OT2", - app_error=False, - robot_error=True, - robot_analysis_error="?", ) OT2_X_v2_13_None_None_PythonSyntaxError: Protocol = Protocol( file_stem="OT2_X_v2_13_None_None_PythonSyntaxError", file_extension="py", robot="OT2", - app_error=True, - robot_error=True, - app_analysis_error="No module named 'superspecialmagic'", - robot_analysis_error="?", ) OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift: Protocol = Protocol( file_stem="OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_7_P20S_None_Walkthrough: Protocol = Protocol( file_stem="OT2_S_v2_7_P20S_None_Walkthrough", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume: Protocol = Protocol( file_stem="OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_12_P300M_P20S_FailOnRun: Protocol = Protocol( file_stem="OT2_S_v2_12_P300M_P20S_FailOnRun", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3: Protocol = Protocol( file_stem="OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, custom_labware=["cpx_4_tuberack_100ul"], ) @@ -182,8 +140,6 @@ class Protocols: file_stem="OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, custom_labware=["cpx_4_tuberack_100ul"], ) @@ -191,8 +147,6 @@ class Protocols: file_stem="OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, custom_labware=["cpx_4_tuberack_100ul"], ) @@ -200,8 +154,6 @@ class Protocols: file_stem="OT2_S_v2_16_P300M_P20S_HS_TC_TM_SmokeTestV3", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, custom_labware=["cpx_4_tuberack_100ul"], ) @@ -209,16 +161,13 @@ class Protocols: file_stem="OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, + custom_labware=["cpx_4_tuberack_100ul"], ) OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release: Protocol = Protocol( file_stem="OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, custom_labware=["cpx_4_tuberack_100ul"], ) @@ -226,561 +175,396 @@ class Protocols: file_stem="OT2_S_v2_4_P300M_None_MM_TM_Zymo", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError: Protocol = Protocol( file_stem="OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError", file_extension="py", robot="OT2", - app_error=True, - robot_error=True, - app_analysis_error="DeckConflictError [line 19]: thermocyclerModuleV2 in slot 7 prevents thermocyclerModuleV1 from using slot 7.", # noqa: E501 - robot_analysis_error="?", ) OT2_X_v2_7_P300S_TwinningError: Protocol = Protocol( file_stem="OT2_X_v2_7_P300S_TwinningError", file_extension="py", robot="OT2", - app_error=True, - robot_error=True, - app_analysis_error="AttributeError [line 24]: 'InstrumentContext' object has no attribute 'pair_with'", - robot_analysis_error="?", ) OT2_S_v2_2_P300S_None_MM1_MM2_EngageMagHeightFromBase: Protocol = Protocol( file_stem="OT2_S_v2_2_P300S_None_MM1_MM2_EngageMagHeightFromBase", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix: Protocol = Protocol( file_stem="OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume: Protocol = Protocol( file_stem="OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes: Protocol = Protocol( file_stem="OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes: Protocol = Protocol( file_stem="OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_17_P300M_P20S_HS_TC_TM_dispense_changes: Protocol = Protocol( file_stem="OT2_S_v2_17_P300M_P20S_HS_TC_TM_dispense_changes", file_extension="py", robot="OT2", - app_error=True, - robot_error=False, - app_analysis_error="ValueError [line 15]: Cannot dispense more than pipette max volume", # noqa: E501 ) OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots: Protocol = Protocol( file_stem="OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots: Protocol = Protocol( file_stem="OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots: Protocol = Protocol( file_stem="OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots: Protocol = Protocol( file_stem="OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1: Protocol = Protocol( file_stem="OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1", file_extension="py", robot="OT2", - app_error=True, - robot_error=False, - app_analysis_error="DeckConflictError [line 19]: trash_bin in slot 12 prevents heater_shaker in slot 11 from using slot 11.", # noqa: E501 ) OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin2: Protocol = Protocol( file_stem="OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin2", file_extension="py", robot="OT2", - app_error=True, - robot_error=False, - app_analysis_error="DeckConflictError [line 19]: trash_bin in slot 12 prevents heater_shaker in slot 11 from using slot 11.", # noqa: E501 ) OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock: Protocol = Protocol( file_stem="OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting: Protocol = Protocol( file_stem="OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3: Protocol = Protocol( file_stem="OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, ) - Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction: Protocol = Protocol( - file_stem="Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction", + Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria: Protocol = Protocol( + file_stem="Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_1000ul_rss"], ) - Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction: Protocol = Protocol( - file_stem="Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction", + Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria: Protocol = Protocol( + file_stem="Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_1000ul_rss"], ) Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction: Protocol = Protocol( file_stem="Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_200ul_rss"], ) Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3: Protocol = Protocol( file_stem="Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_200ul_rss", "opentrons_ot3_96_tiprack_50ul_rss"], ) Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3: Protocol = Protocol( file_stem="Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_50ul_rss", "opentrons_ot3_96_tiprack_200ul_rss"], ) - Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4: Protocol = Protocol( - file_stem="Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4", + Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4: Protocol = Protocol( + file_stem="Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) - Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment: Protocol = Protocol( - file_stem="Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment", + Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7: Protocol = Protocol( + file_stem="Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) - Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x: Protocol = Protocol( - file_stem="Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x", + + Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight: Protocol = Protocol( + file_stem="Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) - Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight: Protocol = Protocol( - file_stem="Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight", + + Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8: Protocol = Protocol( + file_stem="Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_200ul_rss"], ) - Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2: Protocol = Protocol( - file_stem="Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2", + + Flex_S_v2_18_P1000_96_GRIP_HS_MB_TC_TM_Smoke: Protocol = Protocol( + file_stem="Flex_S_v2_18_P1000_96_GRIP_HS_MB_TC_TM_Smoke", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2: Protocol = Protocol( file_stem="Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="InvalidTrashBinLocationError [line 15]: Invalid location for trash bin: C2. Valid slots: Any slot in column 1 or 3.", # noqa: E501 ) Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3: Protocol = Protocol( file_stem="Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="ProtocolCommandFailedError [line 21]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): IncompatibleAddressableAreaError: Cannot use Trash Bin in C3, not compatible with one or more of the following fixtures: Slot C4", # noqa: E501 - expected_test_failure=True, - expected_test_reason="Analysis does not throw error when modules or fixtures are in staging area column 3.", # noqa: E501 ) Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4: Protocol = Protocol( file_stem="Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="ValueError [line 15]: Staging areas not permitted for trash bin.", # noqa: E501 ) Flex_X_v2_16_P1000_96_DropTipsWithNoTrash: Protocol = Protocol( file_stem="Flex_X_v2_16_P1000_96_DropTipsWithNoTrash", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="NoTrashDefinedError [line 24]: Error 4000 GENERAL_ERROR (NoTrashDefinedError): No trash container has been defined in this protocol.", # noqa: E501 ) Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3: Protocol = Protocol( file_stem="Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="InvalidModuleError [line 19]: Error 4000 GENERAL_ERROR (InvalidModuleError): Cannot use temperature module in C3, not compatible with one or more of the following fixtures: Slot C4", # noqa: E501 - expected_test_failure=True, - expected_test_reason="Analysis does not throw error when modules or fixtures are in staging area column 3.", # noqa: E501 ) Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4: Protocol = Protocol( file_stem="Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="ValueError [line 15]: Cannot load a module onto a staging slot.", # noqa: E501 ) Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict: Protocol = Protocol( file_stem="Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="ProtocolCommandFailedError [line 25]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): IncompatibleAddressableAreaError: Cannot use Waste Chute, not compatible with one or more of the following fixtures: Slot D3", # noqa: E501 ) Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp: Protocol = Protocol( file_stem="Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="APIVersionError [line 15]: Fixed Trash is not supported on Flex protocols in API Version 2.16 and above.", # noqa: E501 ) Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin: Protocol = Protocol( file_stem="Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="ProtocolCommandFailedError [line 20]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): IncompatibleAddressableAreaError: Cannot use Slot C3, not compatible with one or more of the following fixtures: Trash Bin in C3", # noqa: E501 ) Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol: Protocol = Protocol( file_stem="Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="ProtocolCommandFailedError [line 22]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): InvalidSpecificationForRobotTypeError: Cannot load a Gen2 pipette on a Flex.", # noqa: E501 ) Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol: Protocol = Protocol( file_stem="Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="ValueError [line 15]: A magneticModuleType cannot be loaded into slot C1", # noqa: E501 ) Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2: Protocol = Protocol( file_stem="Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="ValueError [line 15]: A temperatureModuleType cannot be loaded into slot C2", # noqa: E501 ) Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures: Protocol = Protocol( file_stem="Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules: Protocol = Protocol( file_stem="Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures: Protocol = Protocol( file_stem="Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1: Protocol = Protocol( file_stem="Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke: Protocol = Protocol( file_stem="Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots: Protocol = Protocol( file_stem="Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots: Protocol = Protocol( file_stem="Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots: Protocol = Protocol( file_stem="Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict: Protocol = Protocol( file_stem="Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="IncompatibleAddressableAreaError [line 15]: Cannot use Trash Bin in C3, not compatible with one or more of the following fixtures: Thermocycler in C3", # noqa: E501 ) Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips: Protocol = Protocol( file_stem="Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="IncompatibleAddressableAreaError [line 15]: Cannot use Slot C3, not compatible with one or more of the following fixtures: Thermocycler in C3", # noqa: E501 ) Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid: Protocol = Protocol( file_stem="Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="IncompatibleAddressableAreaError [line 15]: Cannot use Slot C3, not compatible with one or more of the following fixtures: Thermocycler in C3", # noqa: E501 ) Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle: Protocol = Protocol( file_stem="Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn: Protocol = Protocol( file_stem="Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip: Protocol = Protocol( file_stem="Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="ValueError [line 15]: Cannot return tip in partial tip pickup mode.", # noqa: E501 ) Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict: Protocol = Protocol( file_stem="Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict", file_extension="py", robot="Flex", - app_error=True, - robot_error=False, - app_analysis_error="IncompatibleAddressableAreaError [line 15]: Cannot use Slot C3, not compatible with one or more of the following fixtures: Thermocycler in C3", # noqa: E501 ) Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_TriggerPrepareForMountMovement: Protocol = Protocol( file_stem="Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_TriggerPrepareForMountMovement", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_18_NO_PIPETTES_GoldenRTP: Protocol = Protocol( file_stem="Flex_S_v2_18_NO_PIPETTES_GoldenRTP", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_X_v2_18_NO_PIPETTES_DescriptionTooLongRTP: Protocol = Protocol( file_stem="Flex_X_v2_18_NO_PIPETTES_DescriptionTooLongRTP", file_extension="py", robot="Flex", - app_error=True, - robot_error=True, ) Flex_S_v2_18_P1000_96_TipTrackingBug: Protocol = Protocol( file_stem="Flex_S_v2_18_P1000_96_TipTrackingBug", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_X_v2_18_NO_PIPETTES_ReservedWord: Protocol = Protocol( file_stem="Flex_X_v2_18_NO_PIPETTES_ReservedWord", file_extension="py", robot="Flex", - app_error=True, - robot_error=True, ) Flex_S_v2_18_AMPure_XP_48x: Protocol = Protocol( file_stem="Flex_S_v2_18_AMPure_XP_48x", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_19_AMPure_XP_48x: Protocol = Protocol( file_stem="Flex_S_v2_19_AMPure_XP_48x", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_18_IDT_xGen_EZ_48x: Protocol = Protocol( file_stem="Flex_S_v2_18_IDT_xGen_EZ_48x", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_19_IDT_xGen_EZ_48x: Protocol = Protocol( file_stem="Flex_S_v2_19_IDT_xGen_EZ_48x", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_18_Illumina_DNA_Prep_48x: Protocol = Protocol( file_stem="Flex_S_v2_18_Illumina_DNA_Prep_48x", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_19_Illumina_DNA_Prep_48x: Protocol = Protocol( file_stem="Flex_S_v2_19_Illumina_DNA_Prep_48x", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero: Protocol = Protocol( file_stem="Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, custom_labware=["eppendorf_96_well_plate_150_µl"], ) @@ -788,8 +572,6 @@ class Protocols: file_stem="Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, custom_labware=["eppendorf_96_well_plate_150_µl"], ) @@ -797,126 +579,475 @@ class Protocols: file_stem="Flex_S_v2_18_KAPA_Library_Quant", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_19_KAPA_Library_Quant: Protocol = Protocol( file_stem="Flex_S_v2_19_KAPA_Library_Quant", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_18_kapahyperplus: Protocol = Protocol( file_stem="Flex_S_v2_18_kapahyperplus", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_19_kapahyperplus: Protocol = Protocol( file_stem="Flex_S_v2_19_kapahyperplus", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_18_ligseq: Protocol = Protocol( file_stem="Flex_S_v2_18_ligseq", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_19_ligseq: Protocol = Protocol( file_stem="Flex_S_v2_19_ligseq", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_18_QIASeq_FX_48x: Protocol = Protocol( file_stem="Flex_S_v2_18_QIASeq_FX_48x", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, ) Flex_S_v2_19_QIASeq_FX_48x: Protocol = Protocol( file_stem="Flex_S_v2_19_QIASeq_FX_48x", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, + ) Flex_S_v2_18_Illumina_DNA_PCR_Free: Protocol = Protocol( file_stem="Flex_S_v2_18_Illumina_DNA_PCR_Free", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, + ) Flex_S_v2_19_Illumina_DNA_PCR_Free: Protocol = Protocol( file_stem="Flex_S_v2_19_Illumina_DNA_PCR_Free", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, + ) Flex_S_v2_18_P1000_96_GRIP_HS_MB_TC_TM_Smoke: Protocol = Protocol( file_stem="Flex_S_v2_18_P1000_96_GRIP_HS_MB_TC_TM_Smoke", file_extension="py", robot="Flex", - app_error=False, - robot_error=False, + + ) + + Flex_S_v2_18_AMPure_XP_48x: Protocol = Protocol( + file_stem="Flex_S_v2_18_AMPure_XP_48x", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_19_AMPure_XP_48x: Protocol = Protocol( + file_stem="Flex_S_v2_19_AMPure_XP_48x", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_18_IDT_xGen_EZ_48x: Protocol = Protocol( + file_stem="Flex_S_v2_18_IDT_xGen_EZ_48x", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_19_IDT_xGen_EZ_48x: Protocol = Protocol( + file_stem="Flex_S_v2_19_IDT_xGen_EZ_48x", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_18_Illumina_DNA_Prep_48x: Protocol = Protocol( + file_stem="Flex_S_v2_18_Illumina_DNA_Prep_48x", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_19_Illumina_DNA_Prep_48x: Protocol = Protocol( + file_stem="Flex_S_v2_19_Illumina_DNA_Prep_48x", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero: Protocol = Protocol( + file_stem="Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero", + file_extension="py", + robot="Flex", + + custom_labware=["eppendorf_96_well_plate_150_µl"], + ) + + Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero: Protocol = Protocol( + file_stem="Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero", + file_extension="py", + robot="Flex", + + custom_labware=["eppendorf_96_well_plate_150_µl"], + ) + + Flex_S_v2_18_KAPA_Library_Quant: Protocol = Protocol( + file_stem="Flex_S_v2_18_KAPA_Library_Quant", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_19_KAPA_Library_Quant: Protocol = Protocol( + file_stem="Flex_S_v2_19_KAPA_Library_Quant", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_18_kapahyperplus: Protocol = Protocol( + file_stem="Flex_S_v2_18_kapahyperplus", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_19_kapahyperplus: Protocol = Protocol( + file_stem="Flex_S_v2_19_kapahyperplus", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_18_ligseq: Protocol = Protocol( + file_stem="Flex_S_v2_18_ligseq", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_19_ligseq: Protocol = Protocol( + file_stem="Flex_S_v2_19_ligseq", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_18_QIASeq_FX_48x: Protocol = Protocol( + file_stem="Flex_S_v2_18_QIASeq_FX_48x", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_19_QIASeq_FX_48x: Protocol = Protocol( + file_stem="Flex_S_v2_19_QIASeq_FX_48x", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_18_Illumina_DNA_PCR_Free: Protocol = Protocol( + file_stem="Flex_S_v2_18_Illumina_DNA_PCR_Free", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_19_Illumina_DNA_PCR_Free: Protocol = Protocol( + file_stem="Flex_S_v2_19_Illumina_DNA_PCR_Free", + file_extension="py", + robot="Flex", + + ) + + Flex_S_v2_18_P1000_96_GRIP_HS_MB_TC_TM_Smoke: Protocol = Protocol( + file_stem="Flex_S_v2_18_P1000_96_GRIP_HS_MB_TC_TM_Smoke", + file_extension="py", + robot="Flex", + ) OT2_X_v2_18_None_None_duplicateRTPVariableName: Protocol = Protocol( file_stem="OT2_X_v2_18_None_None_duplicateRTPVariableName", file_extension="py", robot="OT2", - app_error=True, - robot_error=True, ) OT2_S_v2_18_None_None_duplicateChoiceValue: Protocol = Protocol( file_stem="OT2_S_v2_18_None_None_duplicateChoiceValue", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, + ) + OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3: Protocol = Protocol( + file_stem="OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3", + file_extension="py", + robot="OT2", ) OT2_X_v2_18_None_None_StrRTPwith_unit: Protocol = Protocol( file_stem="OT2_X_v2_18_None_None_StrRTPwith_unit", file_extension="py", robot="OT2", - app_error=True, - robot_error=True, ) OT2_X_v2_18_None_None_NoRTPdisplay_name: Protocol = Protocol( file_stem="OT2_X_v2_18_None_None_NoRTPdisplay_name", file_extension="py", robot="OT2", - app_error=True, - robot_error=True, ) OT2_S_v2_18_NO_PIPETTES_GoldenRTP_OT2: Protocol = Protocol( file_stem="OT2_S_v2_18_NO_PIPETTES_GoldenRTP_OT2", file_extension="py", robot="OT2", - app_error=False, - robot_error=False, + ) + + ########################################################################################################## + # Begin Protocol Library Protocols ####################################################################### + ########################################################################################################## + pl_96_ch_demo_rtp_with_hs: Protocol = Protocol( + file_stem="pl_96_ch_demo_rtp_with_hs", + file_extension="py", + robot="Flex", + ) + pl_96_ch_demo_rtp: Protocol = Protocol( + file_stem="pl_96_ch_demo_rtp", + file_extension="py", + robot="Flex", + ) + pl_AMPure_XP_48x_v8: Protocol = Protocol( + file_stem="pl_AMPure_XP_48x_v8", + file_extension="py", + robot="Flex", + ) + pl_BacteriaInoculation_Flex_6plates: Protocol = Protocol( + file_stem="pl_BacteriaInoculation_Flex_6plates", + file_extension="py", + robot="Flex", + custom_labware=["Omni_Plate_with_Tilt_Adapter", "Omni_Tray_Single_Well_Plate"], + ) + pl_BCApeptideassay: Protocol = Protocol( + file_stem="pl_BCApeptideassay", + file_extension="py", + robot="Flex", + ) + pl_Bradford_proteinassay: Protocol = Protocol( + file_stem="pl_Bradford_proteinassay", + file_extension="py", + robot="Flex", + ) + pl_cherrypicking_csv_airgap: Protocol = Protocol( + file_stem="pl_cherrypicking_csv_airgap", + file_extension="py", + robot="Flex", + ) + pl_Dynabeads_IP_Flex_96well_final: Protocol = Protocol( + file_stem="pl_Dynabeads_IP_Flex_96well_final", + file_extension="py", + robot="Flex", + ) + pl_Dynabeads_IP_Flex_96well_RIT_final: Protocol = Protocol( + file_stem="pl_Dynabeads_IP_Flex_96well_RIT_final", + file_extension="py", + robot="Flex", + ) + pl_EM_seq_48Samples_AllSteps_Edits_150: Protocol = Protocol( + file_stem="pl_EM_seq_48Samples_AllSteps_Edits_150", + file_extension="py", + robot="Flex", + ) + pl_ExpressPlex_96_final: Protocol = Protocol( + file_stem="pl_ExpressPlex_96_final", + file_extension="py", + robot="Flex", + custom_labware=["BioRad_96_Well_Plate_200_uL"], + ) + pl_ExpressPlex_Pooling_Final: Protocol = Protocol( + file_stem="pl_ExpressPlex_Pooling_Final", + file_extension="py", + robot="Flex", + ) + pl_Flex_customizable_serial_dilution_upload: Protocol = Protocol( + file_stem="pl_Flex_customizable_serial_dilution_upload", + file_extension="py", + robot="Flex", + ) + pl_Flex_Protein_Digestion_Protocol: Protocol = Protocol( + file_stem="pl_Flex_Protein_Digestion_Protocol", + file_extension="py", + robot="Flex", + custom_labware=["Thermo_96_Well_Plate_2200_uL"], + ) + pl_Hyperplus_tiptracking_V4_final: Protocol = Protocol( + file_stem="pl_Hyperplus_tiptracking_V4_final", + file_extension="py", + robot="Flex", + ) + pl_Illumina_DNA_PCR_Free: Protocol = Protocol( + file_stem="pl_Illumina_DNA_PCR_Free", + file_extension="py", + robot="Flex", + ) + pl_Illumina_DNA_Prep_48x_v8: Protocol = Protocol( + file_stem="pl_Illumina_DNA_Prep_48x_v8", + file_extension="py", + robot="Flex", + ) + pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol: Protocol = Protocol( + file_stem="pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol", + file_extension="py", + robot="Flex", + custom_labware=["Eppendorf_96_Well_Plate_150_uL"], + ) + pl_KAPA_Library_Quant_48_v8: Protocol = Protocol( + file_stem="pl_KAPA_Library_Quant_48_v8", + file_extension="py", + robot="Flex", + ) + pl_langone_pt2_ribo: Protocol = Protocol( + file_stem="pl_langone_pt2_ribo", + file_extension="py", + robot="Flex", + ) + pl_langone_ribo_pt1_ramp: Protocol = Protocol( + file_stem="pl_langone_ribo_pt1_ramp", + file_extension="py", + robot="Flex", + ) + pl_M_N_Nucleomag_DNA_Flex_multi: Protocol = Protocol( + file_stem="pl_M_N_Nucleomag_DNA_Flex_multi", + file_extension="py", + robot="Flex", + custom_labware=["Macherey_Nagel_deepwell_plate_2200ul"], + ) + pl_MagMax_RNA_Cells_Flex_96_Channel: Protocol = Protocol( + file_stem="pl_MagMax_RNA_Cells_Flex_96_Channel", + file_extension="py", + robot="Flex", + ) + pl_MagMax_RNA_Cells_Flex_multi: Protocol = Protocol( + file_stem="pl_MagMax_RNA_Cells_Flex_multi", + file_extension="py", + robot="Flex", + ) + pl_microBioID_beads_touchtip: Protocol = Protocol( + file_stem="pl_microBioID_beads_touchtip", + file_extension="py", + robot="Flex", + ) + pl_Nanopore_Genomic_Ligation_v5_Final: Protocol = Protocol( + file_stem="pl_Nanopore_Genomic_Ligation_v5_Final", + file_extension="py", + robot="Flex", + ) + pl_NiNTA_Flex_96well_final: Protocol = Protocol( + file_stem="pl_NiNTA_Flex_96well_final", + file_extension="py", + robot="Flex", + ) + pl_NiNTA_Flex_96well_PlatePrep_final: Protocol = Protocol( + file_stem="pl_NiNTA_Flex_96well_PlatePrep_final", + file_extension="py", + robot="Flex", + ) + pl_Normalization_with_PCR: Protocol = Protocol( + file_stem="pl_Normalization_with_PCR", + file_extension="py", + robot="Flex", + custom_labware=["Axygen_96_Well_Plate_200_uL"], + ) + pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel: Protocol = Protocol( + file_stem="pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel", + file_extension="py", + robot="Flex", + ) + pl_Omega_HDQ_DNA_Bacteria_Flex_multi: Protocol = Protocol( + file_stem="pl_Omega_HDQ_DNA_Bacteria_Flex_multi", + file_extension="py", + robot="Flex", + ) + pl_Omega_HDQ_DNA_Cells_Flex_96_channel: Protocol = Protocol( + file_stem="pl_Omega_HDQ_DNA_Cells_Flex_96_channel", + file_extension="py", + robot="Flex", + ) + pl_Omega_HDQ_DNA_Cells_Flex_multi: Protocol = Protocol( + file_stem="pl_Omega_HDQ_DNA_Cells_Flex_multi", + file_extension="py", + robot="Flex", + ) + pl_QIAseq_FX_24x_Normalizer_Workflow_B: Protocol = Protocol( + file_stem="pl_QIAseq_FX_24x_Normalizer_Workflow_B", + file_extension="py", + robot="Flex", + ) + pl_QIASeq_FX_48x_v8: Protocol = Protocol( + file_stem="pl_QIASeq_FX_48x_v8", + file_extension="py", + robot="Flex", + ) + pl_sample_dilution_with_96_channel_pipette: Protocol = Protocol( + file_stem="pl_sample_dilution_with_96_channel_pipette", + file_extension="py", + robot="Flex", + ) + pl_SamplePrep_MS_Cleanup_Flex_upto96: Protocol = Protocol( + file_stem="pl_SamplePrep_MS_Cleanup_Flex_upto96", + file_extension="py", + robot="Flex", + ) + pl_SamplePrep_MS_Digest_Flex_upto96: Protocol = Protocol( + file_stem="pl_SamplePrep_MS_Digest_Flex_upto96", + file_extension="py", + robot="Flex", + ) + pl_sigdx_part2: Protocol = Protocol(file_stem="pl_sigdx_part2", file_extension="py", robot="Flex") + pl_Takara_InFusionSnapAssembly_Flex: Protocol = Protocol( + file_stem="pl_Takara_InFusionSnapAssembly_Flex", + file_extension="py", + robot="Flex", + ) + pl_cherrypicking_flex_v3: Protocol = Protocol( + file_stem="pl_cherrypicking_flex_v3", + file_extension="py", + robot="Flex", + ) + + pl_normalization_with_csv: Protocol = Protocol( + file_stem="pl_normalization_with_csv", + file_extension="py", + robot="Flex", + ) + pl_Zymo_Magbead_DNA_Cells_Flex_96_channel: Protocol = Protocol( + file_stem="pl_Zymo_Magbead_DNA_Cells_Flex_96_channel", + file_extension="py", + robot="Flex", + ) + pl_Zymo_Magbead_DNA_Cells_Flex_multi: Protocol = Protocol( + file_stem="pl_Zymo_Magbead_DNA_Cells_Flex_multi", + file_extension="py", + robot="Flex", + ) + pl_Zymo_Quick_RNA_Cells_Flex_96_Channel: Protocol = Protocol( + file_stem="pl_Zymo_Quick_RNA_Cells_Flex_96_Channel", + file_extension="py", + robot="Flex", + ) + pl_Zymo_Quick_RNA_Cells_Flex_multi: Protocol = Protocol( + file_stem="pl_Zymo_Quick_RNA_Cells_Flex_multi", + file_extension="py", + robot="Flex", ) diff --git a/app-testing/automation/data/protocols_with_overrides.py b/app-testing/automation/data/protocols_with_overrides.py index 2c6133180ad..ff4a900e58d 100644 --- a/app-testing/automation/data/protocols_with_overrides.py +++ b/app-testing/automation/data/protocols_with_overrides.py @@ -6,8 +6,6 @@ class ProtocolsWithOverrides: file_stem="Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP", file_extension="py", robot="Flex", - app_error=True, - robot_error=True, override_variable_name="type_to_test", overrides=[ "wrong_type_in_display_name", @@ -26,8 +24,6 @@ class ProtocolsWithOverrides: file_stem="Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultOutOfRangeRTP", file_extension="py", robot="Flex", - app_error=True, - robot_error=True, override_variable_name="type_to_test", overrides=[ "default_greater_than_maximum", @@ -39,8 +35,6 @@ class ProtocolsWithOverrides: file_stem="Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice", file_extension="py", robot="Flex", - app_error=True, - robot_error=True, override_variable_name="type_to_test", overrides=["str_default_no_matching_choices", "float_default_no_matching_choices", "int_default_no_matching_choices"], ) diff --git a/app-testing/automation/driver/__init__.py b/app-testing/automation/driver/__init__.py deleted file mode 100644 index 1baefafe69c..00000000000 --- a/app-testing/automation/driver/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""WebDiver.""" diff --git a/app-testing/automation/driver/base.py b/app-testing/automation/driver/base.py deleted file mode 100644 index 3ec9317b835..00000000000 --- a/app-testing/automation/driver/base.py +++ /dev/null @@ -1,307 +0,0 @@ -"""Base tools to find and interact with elements. - -Expose clear information upon failure. -""" - -from __future__ import annotations - -import os -import time -from pathlib import Path -from typing import Any, Callable, List, Optional, Tuple - -from rich.console import Console -from selenium.common.exceptions import ( - NoSuchElementException, - StaleElementReferenceException, -) -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.remote.webelement import WebElement -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.wait import WebDriverWait - - -class Element: - """Describe an element with words and a locator.""" - - def __init__(self, locator: Tuple[str, str], description: str) -> None: - """Instantiate the Element with locator and description.""" - self.locator: Tuple[str, str] = locator - self.description: str = description - - -Func = Callable[..., WebElement] - - -class Base: - """Object to hold common functionality for WebDriver actions and error handling.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Instantiate the Base. - - With driver, console and a unique identifier for output. - """ - self.driver: WebDriver = driver - self.console: Console = console - self.execution_id = execution_id - - def apply_border_to_locator( - self, - finder: Callable[..., WebElement], - effect_time_sec: float = float(os.getenv("HIGHLIGHT_SECONDS", "2")), - color: str = "magenta", - border_size_px: int = 3, - screenshot: bool = False, - screenshot_message: str = "", - ) -> None: - """Highlights (blinks) a Selenium Webdriver element.""" - - def apply_style(argument: str) -> None: - """Execute the javascript to apply the style.""" - self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);", finder(), argument) # type: ignore - - original_style = finder().get_attribute("style") - apply_style(f"border: {border_size_px}px solid {color};") - if screenshot: - self.take_screenshot(message=screenshot_message) - time.sleep(effect_time_sec) - if original_style is None: - apply_style("") - else: - apply_style(original_style) - - def apply_border_to_element( - self, - element: WebElement, - effect_time_sec: float = float(os.getenv("HIGHLIGHT_SECONDS", "2")), - color: str = "magenta", - border_size_px: int = 3, - screenshot: bool = False, - screenshot_message: str = "", - ) -> None: - """Highlights (blinks) a Selenium Webdriver element.""" - - def apply_style(argument: str) -> None: - """Execute the javascript to apply the style.""" - self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);", element, argument) # type: ignore - - original_style = element.get_attribute("style") - apply_style(f"border: {border_size_px}px solid {color};") - if screenshot: - self.take_screenshot(message=screenshot_message) - time.sleep(effect_time_sec) - if original_style is None: - apply_style("") - else: - apply_style(original_style) - - def highlight_element(self, finder: Callable[..., WebElement]) -> None: - """Highlight an element.""" - slow_mo: Optional[str] = os.getenv("SLOWMO") - if slow_mo: - if slow_mo.lower() == "true": - self.apply_border_to_locator(finder) - - def highlight_elements(self, finder: List[WebElement]) -> None: - """Highlight an element.""" - slow_mo: Optional[str] = os.getenv("SLOWMO") - if slow_mo: - if slow_mo.lower() == "true": - for ele in finder: - self.apply_border_to_element(ele) - - def take_screenshot(self, message: str = "") -> None: - """Take a screenshot and place in the results directory.""" - directory_for_results: Path = Path(Path(__file__).resolve().parents[2], "results") - workspace = os.getenv("GITHUB_WORKSPACE", None) - if workspace is not None: - # We are in a GitHub action. - directory_for_results = Path(workspace, "test", "results") - if not os.path.exists(directory_for_results): - os.makedirs(directory_for_results) - note = "" if (message == "") else f"_{message}".replace(" ", "_") - file_name = ( - f"{str(time.time_ns())[:-3]}_{self.execution_id}".replace("/", "_").replace("::", "__").replace(".py", "") + note + ".png" - ) - screenshot_full_path: str = str(Path(directory_for_results, file_name)) - self.console.print(f"screenshot saved: {file_name}", style="white on blue") - self.driver.save_screenshot(screenshot_full_path) - - def click(self, element: Element) -> None: - r"""Highlight the element to click. - - 1. highlight the element \n - 2. screenshot \n - 2. un-highlight \n - 3. click \n - 4. screenshot - """ - finder = self.create_finder(element, EC.element_to_be_clickable) - self.apply_border_to_locator( - finder, - effect_time_sec=0, - screenshot=True, - screenshot_message="item to click", - ) - finder().click() - self.take_screenshot( - message="after click", - ) - - def click_webelement(self, element: WebElement) -> None: - r"""Highlight the element to click. - - 1. highlight the element \n - 2. screenshot \n - 2. un-highlight \n - 3. click \n - 4. screenshot - """ - self.apply_border_to_element( - element=element, - effect_time_sec=0, - screenshot=True, - screenshot_message="item to click", - ) - element.click() - self.take_screenshot(message="after click") - - def output_exception( - self, - element: Element, - ) -> None: - """Handle exceptions for locators.""" - self.console.print(f"Issue finding {element.description}", style="white on blue") - self.console.print(f"Locator {element.locator}", style="white on blue") - self.take_screenshot(message="on exception") - self.console.print_exception() - - def create_finder( - self, - element: Element, - expected_condition: Any = EC.presence_of_element_located, - timeout_sec: int = 12, - ) -> Callable[..., WebElement]: - """Create finder function.""" - ignored_exceptions = ( - NoSuchElementException, - StaleElementReferenceException, - ) - - def finder() -> Any: - return WebDriverWait(self.driver, timeout_sec, ignored_exceptions=ignored_exceptions).until(expected_condition(element.locator)) - - return finder - - def clickable_wrapper( - self, - element: Element, - timeout_sec: int = 12, - ) -> WebElement: - """Use the expected condition element_to_be_clickable. Raise on issue.""" - try: - finder = self.create_finder(element, EC.element_to_be_clickable, timeout_sec) - finder() - self.highlight_element(finder) - return finder() - except Exception as e: - self.output_exception(element) - raise e - - def present_wrapper(self, element: Element, timeout_sec: int = 12) -> WebElement: - """Gracefully use the expected condition presence_of_element_located. Raise on issue.""" - try: - finder = self.create_finder(element, timeout_sec=timeout_sec) - finder() - self.highlight_element(finder) - return finder() - except Exception as e: - self.output_exception(element) - raise e - - def find_wrapper(self, element: Element) -> WebElement: - """Gracefully use find_element. Raise on issue.""" - try: - - def finder() -> WebElement: - return self.driver.find_element(*element.locator) - - self.highlight_element(finder) - return finder() - except Exception as e: - self.output_exception(element) - raise e - - def finds_wrapper(self, element: Element) -> List[WebElement]: - """Gracefully use find_elements. Raise on issue.""" - try: - - def finder() -> List[WebElement]: - return self.driver.find_elements(*element.locator) - - self.highlight_elements(finder()) - return finder() - except Exception as e: - self.output_exception(element) - raise e - - def clickable_wrapper_safe( - self, - element: Element, - timeout_sec: int = 12, - ) -> Optional[WebElement]: - """Gracefully use the expected condition element_to_be_clickable.""" - try: - finder = self.create_finder(element, EC.element_to_be_clickable, timeout_sec) - finder() - self.highlight_element(finder) - return finder() - except Exception: - self.output_exception(element) - return None - - def invisible_wrapper_safe( - self, - element: Element, - timeout_sec: int = 12, - ) -> Optional[WebElement]: - """Gracefully use the expected condition element_to_be_clickable.""" - try: - finder = self.create_finder(element, EC.invisibility_of_element_located, timeout_sec) - finder() - self.highlight_element(finder) - return finder() - except Exception: - self.output_exception(element) - return None - - def present_wrapper_safe( - self, - element: Element, - timeout_sec: int = 12, - ) -> Optional[WebElement]: - """Gracefully use the expected condition presence_of_element_located.""" - try: - finder = self.create_finder(element, timeout_sec=timeout_sec) - finder() - self.highlight_element(finder) - return finder() - except Exception: - self.output_exception(element) - return None - - def find_wrapper_safe( - self, - element: Element, - ) -> Optional[WebElement]: - """Gracefully use find_element.""" - try: - - def finder() -> WebElement: - return self.driver.find_element(*element.locator) - - self.highlight_element(finder) - return finder() - except Exception: - self.output_exception(element) - return None diff --git a/app-testing/automation/driver/drag_drop.py b/app-testing/automation/driver/drag_drop.py deleted file mode 100644 index 90d24748a4a..00000000000 --- a/app-testing/automation/driver/drag_drop.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Inject javascript to utilize drag and drop functionality.""" - -from pathlib import Path - -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.remote.webelement import WebElement - -JS_DROP_FILE = """ - var target = arguments[0], - offsetX = arguments[1], - offsetY = arguments[2], - document = target.ownerDocument || document, - window = document.defaultView || window; - - var input = document.createElement('INPUT'); - input.type = 'file'; - input.onchange = function () { - var rect = target.getBoundingClientRect(), - x = rect.left + (offsetX || (rect.width >> 1)), - y = rect.top + (offsetY || (rect.height >> 1)), - dataTransfer = { files: this.files }; - - ['dragenter', 'dragover', 'drop'].forEach(function (name) { - var evt = document.createEvent('MouseEvent'); - evt.initMouseEvent(name, !0, !0, window, 0, 0, 0, x, y, !1, !1, !1, !1, 0, null); - evt.dataTransfer = dataTransfer; - target.dispatchEvent(evt); - }); - - setTimeout(function () { document.body.removeChild(input); }, 25); - }; - document.body.appendChild(input); - return input; -""" - - -def drag_and_drop_file(drop_target: WebElement, path: Path) -> None: - """Use javascript to drag and drop a file. - - https://stackoverflow.com/questions/43382447/python-with-selenium-drag-and-drop-from-file-system-to-webdriver - """ - driver: WebDriver = drop_target.parent - file_input = driver.execute_script(JS_DROP_FILE, drop_target, 0, 0) # type: ignore - file_input.send_keys(str(path.resolve())) diff --git a/app-testing/automation/driver/wait.py b/app-testing/automation/driver/wait.py deleted file mode 100644 index 5213239df3b..00000000000 --- a/app-testing/automation/driver/wait.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Utility to wait. - -https://stackoverflow.com/questions/2785821/is-there-an-easy-way-in-python-to-wait-until-certain-condition-is-true -""" - -import time -from typing import Any, Callable, Optional - -Func = Callable[..., Any] - - -def wait_until( - somepredicate: Func, - timeout_sec: float, - period_sec: float = 0.25, - **kwargs: Optional[int], -) -> bool: - """Wait until some function returns True or timeout.""" - mustend = time.time() + timeout_sec - while time.time() < mustend: - if somepredicate(**kwargs): - return True - time.sleep(period_sec) - return False - - -def wait_until_none( - somepredicate: Func, - timeout_sec: float, - period_sec: float = 0.25, - **kwargs: Optional[int], -) -> bool: - """Wait until some function returns None or timeout.""" - mustend = time.time() + timeout_sec - while time.time() < mustend: - if somepredicate(**kwargs) is None: - return True - time.sleep(period_sec) - return False diff --git a/app-testing/automation/menus/__init__.py b/app-testing/automation/menus/__init__.py deleted file mode 100644 index a7a3689e873..00000000000 --- a/app-testing/automation/menus/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Page Objects for menus.""" diff --git a/app-testing/automation/menus/left_menu.py b/app-testing/automation/menus/left_menu.py deleted file mode 100644 index a567a38a6f6..00000000000 --- a/app-testing/automation/menus/left_menu.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Left Menu Locators.""" - -from typing import Literal - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.by import By - -from automation.driver.base import Base, Element - -PagesLike = Literal["devices", "protocols", "labware", "app-settings"] - - -class LeftMenu: - """Locators for the left side menu.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver, console, and unique id for the test.""" - self.base: Base = Base(driver, console, execution_id) - - protocols: Element = Element((By.XPATH, '//a[contains(@href,"#/protocols")]'), "Left menu Protocols") - labware: Element = Element((By.XPATH, '//a[contains(@href,"#/labware")]'), "Left menu Labware") - devices: Element = Element((By.XPATH, '//a[contains(@href,"#/devices")]'), "Left menu Devices") - gear: Element = Element( - (By.XPATH, '//a[contains(@href,"#/app-settings")]'), - "Left menu bottom gear to go to App Settings", - ) - - def navigate(self, page_name: PagesLike) -> None: - """Use url to navigate.""" - base_url = self.base.driver.current_url.split("#")[0] - self.base.driver.get(f"{base_url}#/{page_name}") - - def dismiss_version_popup(self) -> None: - """Dismiss version available layover if present.""" - not_now = Element( - (By.XPATH, "//button[text()='Not Now']"), - "Not Now button on ap version available layover.", - ) - button = self.base.clickable_wrapper_safe(not_now, 3) - if button is not None: - self.base.console.print("Version available layover present.", style="white on blue") - self.base.click_webelement(button) diff --git a/app-testing/automation/pages/__init__.py b/app-testing/automation/pages/__init__.py deleted file mode 100644 index d9ddacec56d..00000000000 --- a/app-testing/automation/pages/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Page Objects for pages.""" diff --git a/app-testing/automation/pages/app_settings.py b/app-testing/automation/pages/app_settings.py deleted file mode 100644 index fc734321ba4..00000000000 --- a/app-testing/automation/pages/app_settings.py +++ /dev/null @@ -1,501 +0,0 @@ -"""Model for the App Settings page that displays info and settings for the app.""" - -from typing import Optional - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element - - -class AppSettings: - """Elements and actions for the App Settings Page that loads when the app is opened.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - app_settings_header: Element = Element( - (By.XPATH, '//p[text()="App Settings"]'), - "App Settings header text", - ) - - def get_app_settings_header(self) -> WebElement: - """Get the app settings text.""" - return self.base.present_wrapper(self.app_settings_header, 2) - - app_software_version_text: Element = Element( - (By.XPATH, '//p[text()="App Software Version"]'), - "App App Software Version header text", - ) - - def get_app_software_version_text(self) -> WebElement: - """Get the app App Software Version text.""" - return self.base.present_wrapper(self.app_software_version_text, 2) - - app_software_version_value: Element = Element( - (By.ID, "GeneralSettings_currentVersion"), - "App Software Version header value", - ) - - def get_app_software_version_value(self) -> WebElement: - """Get the App Software Version value.""" - return self.base.present_wrapper(self.app_software_version_value, 2) - - link_restore_previous_version: Element = Element( - (By.ID, "GeneralSettings_previousVersionLink"), - "Link to restore the previous app version", - ) - - def get_link_restore_previous_version(self) -> WebElement: - """Get the link to restore the previous version.""" - return self.base.present_wrapper(self.link_restore_previous_version, 2) - - def click_link_restore_previous_version(self) -> None: - """Click on the link to restore the previous version.""" - self.base.click(self.link_restore_previous_version) - - link_app_robot_sync: Element = Element( - (By.ID, "GeneralSettings_appAndRobotSync"), - "Link to keep the robot and app in sync", - ) - - def get_link_app_robot_sync(self) -> WebElement: - """Get the link to keep the robot and app in sync.""" - return self.base.present_wrapper(self.link_app_robot_sync, 2) - - software_update_alert_toggle: Element = Element( - (By.ID, "GeneralSettings_softwareUpdateAlerts"), - "toggle button to get the software update button", - ) - - def get_software_update_alert_toggle(self) -> WebElement: - """Get the toggle to update the software alert.""" - return self.base.present_wrapper(self.software_update_alert_toggle, 5) - - def click_software_update_alert_toggle(self) -> None: - """Click on the software update alert toggle button.""" - self.base.click(self.software_update_alert_toggle) - - connect_to_robot_via_IP_address_text: Element = Element( - (By.XPATH, '//p[text()="Connect to a Robot via IP Address"]'), - "connect to robot via IP address text", - ) - - def get_connect_to_robot_via_IP_address_text(self) -> WebElement: - """Get the connect to robot via IP address.""" - return self.base.present_wrapper(self.connect_to_robot_via_IP_address_text, 2) - - connect_to_robot_via_IP_address_button: Element = Element( - (By.ID, "GeneralSettings_setUpConnection"), - "connect to robot via IP address button", - ) - - def get_connect_to_robot_via_IP_address_button(self) -> WebElement: - """Get the connect to robot via IP address button.""" - return self.base.present_wrapper(self.connect_to_robot_via_IP_address_button, 2) - - def click_connect_to_robot_via_IP_address_button(self) -> None: - """Click on the connect to robot via IP address button.""" - self.base.click(self.connect_to_robot_via_IP_address_button) - - how_to_restore_software_version_modal: Element = Element( - (By.XPATH, '//h3[text()="How to Restore a Previous Software Version"]'), - "How to Restore a Previous Software Version modal", - ) - - def get_how_to_restore_software_version_modal(self) -> WebElement: - """Get the modal for How to Restore a Previous Software Version.""" - return self.base.present_wrapper(self.how_to_restore_software_version_modal, 2) - - software_update_alert_header: Element = Element( - (By.XPATH, '//p[text()="Software Update Alerts"]'), - "Software update alert header", - ) - - def get_software_update_alert_header(self) -> WebElement: - """Get the software update alert header.""" - return self.base.present_wrapper(self.software_update_alert_header, 2) - - connect_robot_via_IP_header: Element = Element( - (By.XPATH, '//p[text()="Connect to a Robot via IP Address"]'), - "Software update alert header", - ) - - def get_connect_robot_via_IP_header(self) -> WebElement: - """Get the software update alert header.""" - return self.base.present_wrapper(self.connect_robot_via_IP_header, 2) - - learn_more_about_uninstalling_opentrons_app: Element = Element( - (By.ID, "PreviousVersionModal_uninstallingAppLink"), - "link to uninstalling the opentrons app", - ) - - def get_learn_more_about_uninstalling_opentrons_app(self) -> WebElement: - """Get the link to uninstalling the opentrons app.""" - return self.base.present_wrapper(self.learn_more_about_uninstalling_opentrons_app, 2) - - link_to_previous_releases: Element = Element( - (By.ID, "PreviousVersionModal_previousReleases"), - "link to previous releases", - ) - - def get_link_to_previous_releases(self) -> WebElement: - """Get the link to previous releases.""" - return self.base.present_wrapper(self.link_to_previous_releases, 2) - - close_previous_software_modal: Element = Element( - (By.ID, "PreviousVersionModal_closeButton"), - "close button in the previous software version modal", - ) - - def get_close_previous_software_modal(self) -> WebElement: - """Get the close button in the previous software version modal.""" - return self.base.present_wrapper(self.close_previous_software_modal, 2) - - def click_close_previous_software_modal(self) -> None: - """Click the close button in the previous software version modal.""" - button: Optional[WebElement] = self.get_close_previous_software_modal() - if button: - button.click() - - connect_to_robot_via_IP_address_slideout_header: Element = Element( - ( - By.XPATH, - "//h2[@data-testid='Slideout_title_Connect to a Robot via IP Address']", - ), - "connect to robot via IP address slideout header text", - ) - - def get_connect_to_robot_via_IP_address_slideout_header( - self, - ) -> WebElement: - """Get the connect to robot via IP address slideout header.""" - return self.base.present_wrapper(self.connect_to_robot_via_IP_address_slideout_header, 2) - - link_learn_more_about_connecting_a_robot_manually: Element = Element( - (By.ID, "ConnectIPAddressSupportPage"), - "the link to connect to robot manually", - ) - - def get_link_learn_more_about_connecting_a_robot_manually( - self, - ) -> WebElement: - """Get the link to connect to robot manually.""" - return self.base.present_wrapper(self.link_learn_more_about_connecting_a_robot_manually, 2) - - textbox_to_enter_the_ip: Element = Element( - (By.ID, "ip"), - "the textbox to enter the IP address", - ) - - def get_textbox_to_enter_the_ip( - self, - ) -> WebElement: - """Get the textbox to enter the IP address.""" - return self.base.present_wrapper(self.textbox_to_enter_the_ip, 2) - - def click_add_ip_or_hostname(self) -> None: - """Click on text box to add ip or hostname.""" - self.base.click(self.textbox_to_enter_the_ip) - - try_again_link: Element = Element( - (By.ID, "AppSettings_Connection_Button"), - "the try again link", - ) - - def get_try_again_link( - self, - ) -> WebElement: - """Get the try again link.""" - return self.base.present_wrapper(self.try_again_link, 31) - - add_button: Element = Element( - (By.XPATH, "//button[text()='Add']"), - "the add button", - ) - - def get_add_button( - self, - ) -> WebElement: - """Get the add button.""" - return self.base.present_wrapper(self.add_button, 2) - - def click_add_button(self) -> None: - """Click on add button.""" - self.base.click(self.add_button) - - ip: Element = Element( - (By.ID, "ip"), - "Input hostname to the slideout.'", - ) - - def enter_hostname(self, hostname: str) -> None: - """Enter hostname text.""" - element = self.base.clickable_wrapper(self.ip, 3) - element.clear() - element.send_keys(hostname) - - done_button: Element = Element( - (By.XPATH, "//button[text()='Done']"), - "the done button", - ) - - def get_done_button( - self, - ) -> WebElement: - """Get the done button.""" - return self.base.present_wrapper(self.done_button, 2) - - def click_done_button(self) -> None: - """Click on add button.""" - self.base.click(self.done_button) - - # Privacy Tab elements - - privacy_tab: Element = Element( - (By.XPATH, '//a[contains(@href,"#/app-settings/privacy")]'), - "privacy tab in App Settings", - ) - - def get_privacy_tab(self) -> WebElement: - """Search for the privacy tab in app-settings.""" - return self.base.clickable_wrapper(self.privacy_tab, 5) - - def click_privacy_tab(self) -> None: - """Click on the privacy tab in app-settings.""" - self.base.click(self.privacy_tab) - - robot_app_analytics: Element = Element( - (By.XPATH, '//p[text()="Share Robot and App Analytics with Opentrons"]'), - "the robot and app analytics with Opentrons", - ) - - def get_robot_app_analytics(self) -> WebElement: - """Get the robot and app analytics with Opentrons.""" - return self.base.present_wrapper(self.robot_app_analytics, 2) - - robot_app_analytics_toggle: Element = Element( - (By.ID, "PrivacySettings_analytics"), - "the robot and app analytics with Opentrons toggle", - ) - - def get_robot_app_analytics_toggle(self) -> WebElement: - """Get the robot and app analytics with Opentrons toggle.""" - return self.base.present_wrapper(self.robot_app_analytics_toggle, 2) - - def click_robot_app_analytics(self) -> None: - """Click on the robot app analytics.""" - self.base.click(self.robot_app_analytics_toggle) - - # Advanced Tab elements - - advanced_tab: Element = Element( - (By.XPATH, '//a[contains(@href,"#/app-settings/advanced")]'), - "advanced tab in App Settings", - ) - - def get_advanced_tab(self) -> WebElement: - """Search for the advanced tab in app-settings.""" - return self.base.clickable_wrapper(self.advanced_tab, 5) - - def click_advanced_tab(self) -> None: - """Click on the advanced tab.""" - self.base.click(self.advanced_tab) - - update_channel: Element = Element( - (By.ID, "AdvancedSettings_updatedChannel"), - "the update channel", - ) - - def get_update_channel(self) -> WebElement: - """Get the update channel on advanced tab.""" - return self.base.present_wrapper(self.update_channel, 2) - - update_channel_latest_stable: Element = Element( - (By.NAME, "__UpdateChannel__"), - "the update channel latest stable", - ) - - def get_update_channel_latest_stable(self) -> WebElement: - """Get the update channel latest stable on advanced tab.""" - return self.base.present_wrapper(self.update_channel_latest_stable, 2) - - additional_custom_labware_source_folder: Element = Element( - (By.ID, "AdvancedSettings_customLabware"), - "the additional custom labware source folder on advanced tab", - ) - - def get_additional_custom_labware_source_folder(self) -> WebElement: - """Get the additional custom labware source folder on advanced tab.""" - return self.base.present_wrapper(self.additional_custom_labware_source_folder, 2) - - change_labware_source_folder_button: Element = Element( - (By.ID, "AdvancedSettings_changeLabwareSource"), - "the change labware source folder on advanced tab", - ) - - def get_change_labware_source_folder_button(self) -> WebElement: - """Get the change labware source folder on advanced tab.""" - return self.base.present_wrapper(self.change_labware_source_folder_button, 2) - - additional_source_folder: Element = Element( - (By.ID, "AdvancedSettings_sourceFolderLink"), - "the additional source folder on advanced tab", - ) - - def get_additional_source_folder(self) -> WebElement: - """Get the additional source folder on advanced tab.""" - return self.base.present_wrapper(self.additional_source_folder, 2) - - tip_length_calibration_method: Element = Element( - (By.ID, "AdvancedSettings_tipLengthCalibration"), - "the tip length calibration on advanced tab", - ) - - def get_tip_length_calibration_method(self) -> WebElement: - """Get the tip length calibration on advanced tab.""" - return self.base.present_wrapper(self.tip_length_calibration_method, 2) - - tip_calibration_block_to_calibrate: Element = Element( - (By.XPATH, '//div[text()="Always use calibration block to calibrate"]'), - "Always use calibration block to calibrate", - ) - - def get_tip_calibration_block_to_calibrate(self) -> WebElement: - """Search for the Always use calibration block to calibrate.""" - return self.base.clickable_wrapper(self.tip_calibration_block_to_calibrate, 5) - - def click_tip_calibration_block_to_calibrate(self) -> None: - """Click on Always use calibration block to calibrate option.""" - self.base.click(self.tip_calibration_block_to_calibrate) - - tip_calibration_trash_bin: Element = Element( - (By.XPATH, '//div[text()="Always use trash bin to calibrate"]'), - "Always use trash bin to calibrate", - ) - - def get_tip_calibration_trash_bin(self) -> WebElement: - """Search for Always use trash bin to calibrate.""" - return self.base.clickable_wrapper(self.tip_calibration_trash_bin, 5) - - def click_tip_calibration_trash_bin(self) -> None: - """Click on Always use trash bin to calibrate option.""" - self.base.click(self.tip_calibration_trash_bin) - - tip_calibration_prompt: Element = Element( - ( - By.XPATH, - '//div[text()="Always show the prompt to choose calibration block or trash bin"]', - ), - "Always show the prompt to choose calibration block or trash bin", - ) - - def get_tip_calibration_prompt_choose(self) -> WebElement: - """Search for the Always show the prompt to choose calibration block or trash bin.""" - return self.base.clickable_wrapper(self.tip_calibration_prompt, 5) - - def click_tip_calibration_prompt_choose(self) -> None: - """Click on the Always show the prompt to choose calibration block or trash bin.""" - self.base.click(self.tip_calibration_prompt) - - display_unavailable_robots: Element = Element( - (By.ID, "AdvancedSettings_unavailableRobots"), - "the unavailable robots on advanced tab", - ) - - def get_display_unavailable_robots_header(self) -> WebElement: - """Display unavailable robots.""" - return self.base.present_wrapper(self.display_unavailable_robots, 2) - - display_unavailable_robots_toggle: Element = Element( - (By.ID, "AdvancedSettings_unavailableRobotsToggleButton"), - "the unavailable robots toggle on advanced tab", - ) - - def get_display_unavailable_robots_toggle(self) -> WebElement: - """Display unavailable robots toggle.""" - return self.base.clickable_wrapper(self.display_unavailable_robots_toggle, 2) - - def click_unavailable_robot_toggle(self) -> None: - """Click on the unavailable robot toggle.""" - self.base.click(self.display_unavailable_robots_toggle) - - clear_unavailable_robots_header: Element = Element( - (By.ID, "AdvancedSettings_clearRobots"), - "the clear unavailable robots on advanced tab", - ) - - def get_clear_unavailable_robots_header(self) -> WebElement: - """Clear unavailable robots.""" - return self.base.present_wrapper(self.clear_unavailable_robots_header, 2) - - clear_unavailable_robots_list_button: Element = Element( - (By.ID, "AdvancedSettings_clearUnavailableRobots"), - "the clear unavailable robots toggle on advanced tab", - ) - - def get_clear_unavailable_robots_list_button(self) -> WebElement: - """Clear unavailable robots toggle.""" - return self.base.present_wrapper(self.clear_unavailable_robots_list_button, 2) - - def click_clear_unavailable_robot_button(self) -> None: - """Click on the clear unavailable robot button.""" - self.base.click(self.clear_unavailable_robots_list_button) - - enable_developer_tool_header: Element = Element( - (By.ID, "AdvancedSettings_devTools"), - "the Enable Developer Tools on advanced tab", - ) - - def get_enable_developer_tool_header(self) -> WebElement: - """Enable Developer Tools.""" - return self.base.present_wrapper(self.enable_developer_tool_header, 2) - - enable_developer_tools_toggle: Element = Element( - (By.ID, "AdvancedSettings_devTooltoggle"), - "the Enable Developer Tools toggle on advanced tab", - ) - - def get_enable_developer_tools_toggle(self) -> WebElement: - """Enable Developer Tools toggle.""" - return self.base.clickable_wrapper(self.enable_developer_tools_toggle, 2) - - def click_enable_developer_tools_toggle(self) -> None: - """Click on the Enable Developer Tools toggle. - - This option may not be toggled to off in the mode in which we run these - tests. The click works but has no effect. - """ - button = self.get_enable_developer_tools_toggle() - actions = ActionChains(self.base.driver) - actions.move_to_element(button).perform() - self.base.click(self.enable_developer_tools_toggle) - - # Elements for Feature Flag - - feature_flag_tab: Element = Element( - (By.XPATH, '//a[contains(@href,"#/app-settings/feature-flags")]'), - "advanced tab in App Settings", - ) - - def get_feature_flag_tab(self) -> WebElement: - """Search for the feature flag tab in app-settings.""" - return self.base.clickable_wrapper(self.feature_flag_tab, 3) - - def click_feature_flag_tab(self) -> None: - """Click on the feature flag tab.""" - self.base.click(self.feature_flag_tab) - - release_notes_link: Element = Element( - (By.ID, "GeneralSettings_GitHubLink"), - "Link to release notes", - ) - - def get_release_notes_link(self) -> WebElement: - """Get release notes link.""" - return self.base.present_wrapper(self.release_notes_link, 2) diff --git a/app-testing/automation/pages/deck_calibrate.py b/app-testing/automation/pages/deck_calibrate.py deleted file mode 100644 index fc5941ec89c..00000000000 --- a/app-testing/automation/pages/deck_calibrate.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Model for the screens of deck calibration.""" - -from enum import Enum -from typing import Optional - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element - - -class DeckCalibrationButtonText(Enum): - """All the buttons in the deck calibration sequence. - - In order by declaration here. - """ - - START = "start deck calibration" - CONFIRM = "Confirm placement and continue" - PICK_UP = "Pick up tip" - SLOT_5 = "Yes, move to slot 5" - REMEMBER = "remember z-axis and move to slot 1" - SLOT_3 = "save calibration and move to slot 3" - SLOT_7 = "save calibration and move to slot 7" - SAVE = "save calibration" - EXIT = "Return tip to tip rack and exit" - - -class DeckCalibration: - """Elements and actions for deck calibration.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - def calibration_button(self, button_text: str) -> Element: - """Calibration button.""" - return Element( - (By.XPATH, f"//button[text()='{button_text}']"), - "button with text {button_text}", - ) - - def click_calibration_button(self, text: DeckCalibrationButtonText) -> None: - """Get a calibration button in the sequence by its text.""" - self.base.click(self.calibration_button(text.value)) - - def calibrate_deck(self) -> None: - """Click the buttons in order to complete calibration.""" - for button_text in DeckCalibrationButtonText: - self.console.print( - f"Looking for {button_text.name},{button_text.value}", - style="white on blue", - ) - self.click_calibration_button(button_text) - - exit_button: Element = Element((By.XPATH, "//button[text()='exit']"), "Exit button") - - def get_exit_button_safe(self) -> Optional[WebElement]: - """Find the calibration Exit button or return None.""" - return self.base.clickable_wrapper_safe(self.exit_button) - - exit_confirm_button: Element = Element((By.XPATH, "//button[text()='yes, exit now']"), "exit confirm button") - - def get_exit_confirm_button(self) -> Optional[WebElement]: - """Find the calibration Exit button or return None.""" - return self.base.clickable_wrapper_safe(self.exit_confirm_button) diff --git a/app-testing/automation/pages/device_landing.py b/app-testing/automation/pages/device_landing.py deleted file mode 100644 index 3cb6dc9a570..00000000000 --- a/app-testing/automation/pages/device_landing.py +++ /dev/null @@ -1,758 +0,0 @@ -"""Model for the App page that displays info and settings for the app.""" - -import time -from typing import List, Optional - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element - - -class DeviceLanding: - """Elements and actions for the Page that loads when the app is opened.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - header: Element = Element((By.ID, "DevicesLanding_title"), "Header that is 'Devices'") - - def get_go_to_run_safe(self, robot_name: str) -> Optional[WebElement]: - """Look if a robot has a run by name. Return WebElement or None if not.""" - return self.base.present_wrapper_safe( - self.go_to_run(robot_name), - 5, - ) - - def go_to_run(self, robot_name: str) -> Element: - """If a robot name has a run go to it.""" - return Element( - (By.XPATH, f"//div[@data-testid='RobotCard_{robot_name}_overflowMenu']"), - f"Expander for robot name = {robot_name}", - ) - - def robot_banner(self, robot_name: str) -> Element: - """Robot banner.""" - return Element( - (By.ID, f"RobotStatusBanner_{robot_name}_robotName"), - f"Banner with robot name = {robot_name}", - ) - - def get_robot_banner_safe(self, robot_name: str) -> Optional[WebElement]: - """Look for a robot to be present by name. Return WebElement or None if not.""" - return self.base.present_wrapper_safe( - self.robot_banner(robot_name), - 5, - ) - - def click_robot_banner(self, robot_name: str) -> None: - """Click robot banner by name.""" - self.base.click(self.robot_banner(robot_name)) - - def overflow_menu(self, robot_name: str) -> Element: - """Get the ... overflow menu in the top right of the robot card by Robot name.""" - return Element( - (By.XPATH, f"//div[@data-testid='RobotCard_{robot_name}_overflowMenu']"), - f"Expander for robot name = {robot_name}", - ) - - def get_lights_toggle(self) -> WebElement: - """Get the lights toggle button.""" - lights: Element = Element((By.ID, "RobotOverview_lightsToggle"), "Lights toggle button.") - return self.base.clickable_wrapper(lights, 5) - - def get_robot_image(self, robot_name: str) -> WebElement: - """Get the robot_image.""" - image: Element = Element((By.ID, f"RobotCard_{robot_name}_robotImage"), "Robot image.") - return self.base.clickable_wrapper(image, 5) - - def get_robot_name_device_detail(self, robot_name: str) -> WebElement: - """Get the robot name on device detail page.""" - lights: Element = Element( - (By.ID, "RobotStatusHeader_opentrons-dev_robotName"), - "the robot name on device detail page.", - ) - return self.base.clickable_wrapper(lights, 5) - - def get_left_mount_pipette(self, robot_name: str) -> WebElement: - """Get the left mount pipette.""" - text: Element = Element((By.ID, f"RobotCard_{robot_name}_leftMountPipette"), "Left mount pipette.") - return self.base.clickable_wrapper(text, 5) - - def get_right_mount_pipette(self, robot_name: str) -> WebElement: - """Get the right mount pipette.""" - text: Element = Element( - (By.ID, f"RobotCard_{robot_name}_rightMountPipette"), - "Right mount pipette.", - ) - return self.base.clickable_wrapper(text, 5) - - def get_overflow_button_on_device_landing(self, robot_name: str) -> WebElement: - """Get the overflow button on device landing page.""" - overflow: Element = self.overflow_menu(robot_name) - return self.base.clickable_wrapper(overflow, 5) - - def click_overflow_menu_button_on_device_landing(self, robot_name: str) -> None: - """Click on overflow menu on device landing page.""" - button: WebElement = self.get_overflow_button_on_device_landing(robot_name) - if button: - button.click() - - def get_device_header(self) -> WebElement: - """Get the device header.""" - header: Element = Element((By.ID, "DevicesLanding_title"), "Device header.") - return self.base.clickable_wrapper(header, 5) - - def get_how_to_setup_a_robot(self) -> WebElement: - """Get the how to setup a robot.""" - header: Element = Element( - (By.XPATH, '//a[text()="See how to set up a new robot"]'), - "See how to set up a new robot.", - ) - return self.base.clickable_wrapper(header, 5) - - def get_setup_a_robot_header(self) -> WebElement: - """Get the how to setup a robot.""" - header: Element = Element( - (By.XPATH, '//h3[text()="How to setup a new robot"]'), - "How to set up a new robot.", - ) - return self.base.clickable_wrapper(header, 15) - - def get_link_to_setting_up_a_new_robot(self) -> WebElement: - """Get link for the how to setup a robot.""" - header: Element = Element( - ( - By.XPATH, - '//a[contains(@href,"https://support.opentrons.com/s/ot2-get-started")]', - ), - "link for how to set up a new robot.", - ) - return self.base.clickable_wrapper(header, 5) - - def click_how_to_setup_a_robot(self) -> None: - """Click on the how to setup a robot.""" - button: WebElement = self.get_how_to_setup_a_robot() - if button: - button.click() - - def get_close_button(self) -> WebElement: - """Get the close button.""" - button: Element = Element( - ( - By.XPATH, - '//button[text()="close"]', - ), - "Get the close button.", - ) - return self.base.clickable_wrapper(button, 5) - - def click_close_button(self) -> None: - """Click on the close button.""" - button: WebElement = self.get_close_button() - if button: - button.click() - - def get_lights_status(self) -> bool: - """Return True if toggle is on, False if toggle is off.""" - button = self.get_lights_toggle() - if not button: # None check but the finder throws so should never be hit - return False - # get the status of the toggle - aria: str | None = button.get_attribute("aria-checked") - if not aria: # None check but the finder throws so *should* never be hit - return False - return aria.lower() == "true" - - def set_lights(self, on: bool) -> bool: - """Set the lights toggle. Return a bool of the condition: final light state == the desired state.""" - toggled_on = self.get_lights_status() - if on and toggled_on: # light is already on - return True - if not on and not toggled_on: # light is already off - return True - button = self.get_lights_toggle() - if not button: # None check but the finder raises so this *should* be unreachable - return False - button.click() - # get status of the toggle again - time.sleep(2) # clunky behavior of this toggle - toggled_on = self.get_lights_status() - return toggled_on == on - - def get_pipettes_and_modules_header_text(self) -> str: - """Pipettes and modules header.""" - header: Element = Element((By.ID, "InstrumentsAndModules_title"), "header 'Instruments and Modules'") - element = self.base.clickable_wrapper(header, 5) - if not element: - return "" - return element.text - - def get_image_robot_overview(self) -> WebElement: - """Get the robot image on device detail page.""" - image: Element = Element( - (By.ID, "RobotOverview_robotImage"), - "the robot image on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_recent_protocol_runs_header_text(self) -> str: - """Recent runs header.""" - header: Element = Element((By.ID, "RecentProtocolRuns_title"), "header 'Recent Protocol Runs'") - element = self.base.clickable_wrapper(header, 5) - if not element: - return "" - return element.text - - def get_mag_deck_image(self) -> WebElement: - """Get the mag deck image on device detail page.""" - image: Element = Element( - (By.XPATH, "//img[@alt='magneticModuleV2']"), - "the mag deck image on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_thermocycler_gen1_deck_image(self) -> WebElement: - """Get the thermocycler deck image on device detail page.""" - image: Element = Element( - (By.XPATH, "//img[@alt='thermocyclerModuleV1']"), - "the thermocycler deck image on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_thermocycler_gen2_deck_image(self) -> WebElement: - """Get the thermocycler deck image on device detail page.""" - image: Element = Element( - (By.XPATH, "//img[@alt='thermocyclerModuleV2']"), - "the thermocycler deck image on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_heater_shaker_deck_image(self) -> WebElement: - """Get the thermocycler deck image on device detail page.""" - image: Element = Element( - (By.XPATH, "//img[@alt='heaterShakerModuleV1']"), - "the thermocycler deck image on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_tem_deck_image(self) -> WebElement: - """Get the temp deck image on device detail page.""" - image: Element = Element( - (By.XPATH, "//img[@alt='temperatureModuleV2']"), - "the temp deck image on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_left_mount_pipette_device_detail(self, pipette: str) -> WebElement: - """Get the left mount pipette on device detail page.""" - text: Element = Element( - ( - By.XPATH, - f"//div[@data-testid='PipetteCard_display_name_{pipette}']", - ), - "the left mount pipette on device detail page.", - ) - return self.base.clickable_wrapper(text, 5) - - def get_protocol_name_device_detail_slideout(self) -> WebElement: - """Get the protocol name on device detail page slide out.""" - text: Element = Element( - ( - By.XPATH, - "//div[@data-testid='Slideout_body_Choose protocol to Run on opentrons-dev']//div", - ), - "the protocol name on device detail page slide out.", - ) - return self.base.clickable_wrapper(text, 5) - - def get_right_mount_pipette_device_detail(self, pippette: str) -> WebElement: - """Get the right mount pipette on device detail page.""" - text: Element = Element( - ( - By.XPATH, - f"//div[@data-testid='PipetteCard_display_name_{pippette}']", - ), - "the right mount pipette on device detail page.", - ) - return self.base.clickable_wrapper(text, 5) - - def get_mag_module_name(self) -> WebElement: - """Get the mag module name on device detail page.""" - image: Element = Element( - (By.XPATH, "//p[text()='Magnetic Module GEN2']"), - "the mag module name on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_thermocycler_gen1_module_name(self) -> WebElement: - """Get the thermocycler module name on device detail page.""" - image: Element = Element( - (By.XPATH, "//p[text()='Thermocycler Module']"), - "the mag module name on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_thermocycler_gen2_module_name(self) -> WebElement: - """Get the thermocycler gen2 module name on device detail page.""" - image: Element = Element( - (By.XPATH, "//p[text()='Thermocycler Module GEN2']"), - "the mag module name on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_heater_shaker_module_name(self) -> WebElement: - """Get the heater shaker module name on device detail page.""" - image: Element = Element( - (By.XPATH, "//p[text()='Heater-Shaker Module GEN1']"), - "the mag module name on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_tem_module_name(self) -> WebElement: - """Get the mag module name on device detail page.""" - image: Element = Element( - (By.XPATH, "//p[text()='Temperature Module GEN2']"), - "the mag module name on device detail page.", - ) - return self.base.clickable_wrapper(image, 5) - - def get_current_step_text(self) -> WebElement: - """Get the current step text on run log.""" - text: Element = Element( - (By.XPATH, "//p[text()='Current Step']"), - "the current step text on run log.", - ) - return self.base.clickable_wrapper(text, 5) - - def get_clear_button_run_page(self) -> WebElement: - """Get the clear button on run page.""" - button: Element = Element( - (By.ID, "ProtocolRunHeader_closeRunButton"), - "Get the clear button on run page.", - ) - return self.base.clickable_wrapper(button, 10) - - def get_run_button(self) -> WebElement: - """Get the run button on run page.""" - button: Element = Element( - (By.ID, "ProtocolRunHeader_runControlButton"), - "Get the run button on run page.", - ) - return self.base.clickable_wrapper(button, 5) - - def get_success_banner_run_page(self) -> WebElement: - """Get the success banner on run page.""" - banner: Element = Element( - (By.XPATH, "//div[@data-testid='Banner_success']"), - "Get the success banner on run page.", - ) - return self.base.clickable_wrapper(banner, 15) - - def click_module_actions_button(self, module_serial: str) -> None: - """Click module overflow button.""" - button: Element = Element( - ( - By.XPATH, - f"//div[@data-testid='module_card_overflow_btn_{module_serial}']//button", - ), - "Button to open module actions menu.", - ) - self.base.click(button) - - def click_on_jump_to_current_step(self) -> None: - """Click jump to current step.""" - button: Element = Element( - ( - By.ID, - "RunLog_jumpToCurrentStep", - ), - "Clicking on jump to current step.'", - ) - self.base.click(button) - - def run_a_protocol_on_overflow_button(self, robot_name: str) -> Element: - """Get the overflow button on device landing page.""" - return Element( - ( - By.XPATH, - f"//button[@data-testid='RobotOverflowMenu_{robot_name}_runProtocol']", - ), - "Button to go to setup for run page.", - ) - - def get_run_a_protocol_on_overflow(self, robot_name: str) -> Optional[WebElement]: - """Get the overflow button on device landing page.""" - return self.base.clickable_wrapper_safe( - self.run_a_protocol_on_overflow_button(robot_name), - 3, - ) - - def click_run_a_protocol_on_overflow(self, robot_name: str) -> None: - """Click run a protocol on overflow.""" - self.base.click(self.run_a_protocol_on_overflow_button(robot_name)) - - def click_proceed_to_setup_button_device_landing_page(self) -> None: - """Click proceed to setup on device landing.""" - button: Element = Element( - ( - By.XPATH, - "//button[text()='Proceed to setup']", - ), - "Button to Proceed to setup for set up run page.'", - ) - self.base.click(button) - - def click_clear_protocol_button(self) -> None: - """Click clear protocol.""" - button: Element = Element( - ( - By.ID, - "ProtocolRunHeader_closeRunButton", - ), - "Button to clear the protocol from set up run page.'", - ) - self.base.clickable_wrapper(button, 5) - - def click_mag_engage_height(self, module_serial: str) -> None: - """Click engage height.""" - button: Element = Element( - ( - By.XPATH, - f"//div[@data-testid='module_card_overflow_menu_{module_serial}']//button", - ), - "Button to open set engage height slide out.", - ) - self.base.click(button) - - def enter_mag_engage_height(self, module_serial: str, height: str) -> None: - """Enter magnetic module engage height.""" - input: Element = Element( - ( - By.XPATH, - f"//div[@data-testid='MagneticModuleSlideout_input_field_{module_serial}']//input", - ), - "Input for height on slideout.'", - ) - element = self.base.clickable_wrapper(input) - if element: - element.clear() - element.send_keys(height) - - def click_engage_height_button(self) -> None: - """Click engage height.""" - close: Element = Element( - ( - By.XPATH, - "//button[@data-testid='MagneticModuleSlideout_btn_fatal-attraction']", - ), - "Button to set engage height slideout.'", - ) - self.base.click(close) - - def click_start_run_button(self) -> None: - """Click start run.""" - close: Element = Element( - ( - By.ID, - "ProtocolRunHeader_runControlButton", - ), - "Button to start the run on run page.'", - ) - self.base.click(close) - - def close_mag_slideout(self) -> None: - """Close the mag module slideout.""" - close: Element = Element( - ( - By.XPATH, - "//button[@data-testid='Slideout_icon_close_Set Engage Height for Magnetic Module GEN2']", - ), - "Button to close set engage height slideout.'", - ) - self.base.click(close) - - device_detail_run_a_protocol_button_element: Element = Element( - (By.XPATH, '//button[text()="Run a protocol"]'), - "Run a protocol button.", - ) - - def click_device_detail_run_a_protocol_button(self) -> None: - """Click run a protocol button.""" - self.base.click(self.device_detail_run_a_protocol_button_element) - - def overflow_robot_settings(self, robot_name: str) -> Element: - """Element for robot settings overflow menu item.""" - return Element( - (By.ID, f"RobotOverflowMenu_{robot_name}_robotSettings"), - "Robot Settings button.", - ) - - def click_overflow_robot_settings(self, robot_name: str) -> None: - """Click robot settings overflow menu item.""" - self.base.click(self.overflow_robot_settings(robot_name)) - - recalibrate_deck_button: Element = Element( - (By.XPATH, '//button[text()="Recalibrate deck"]'), - "Recalibrate button.", - ) - - def get_recalibrate_button_safe(self) -> Optional[WebElement]: - """Safely get recalibrate button.""" - return self.base.clickable_wrapper_safe(self.recalibrate_deck_button, 3) - - def click_recalibrate_button(self) -> None: - """Click the recalibrate button.""" - self.base.click(self.recalibrate_deck_button) - - calibrate_deck_button: Element = Element( - (By.XPATH, '//button[text()="Calibrate deck"]'), - "Calibrate button.", - ) - - def click_calibrate_button(self) -> None: - """Click the calibrate button.""" - self.base.click(self.calibrate_deck_button) - - def open_calibration(self) -> None: - """Open calibration regardless of state.""" - recalibrate_button = self.get_recalibrate_button_safe() - if recalibrate_button is not None: - self.console.print("Robot is already calibrated.", style="white on blue") - self.click_recalibrate_button() - else: - self.console.print("Robot is not calibrated.", style="white on blue") - self.click_calibrate_button() - - def is_deck_calibrated(self) -> bool: - """Is the deck calibrated?.""" - if self.get_recalibrate_button_safe() is not None: - return True - return False - - pipette_calibration_overflow_1: Element = Element( - (By.XPATH, '(//button[@aria-label="CalibrationOverflowMenu_button"])[1]'), - "First three dot menu menu button for pipette calibrations.", - ) - - def get_pipette_calibration_overflow_1(self) -> WebElement: - """Get the first pipette three dot menu button.""" - scroll: WebElement = self.base.clickable_wrapper(self.pipette_calibration_overflow_1, 3) - actions = ActionChains(self.base.driver) - actions.move_to_element(scroll).perform() - return scroll - - def click_pipette_calibration_overflow_1(self) -> None: - """Click the pipette calibration overflow button.""" - self.base.click(self.pipette_calibration_overflow_1) - - pipette_calibration_overflow_2: Element = Element( - (By.XPATH, '(//button[@aria-label="CalibrationOverflowMenu_button"])[2]'), - "Second three dot menu menu button for pipette calibrations.", - ) - - def get_pipette_calibration_overflow_2(self) -> WebElement: - """Get the first pipette three dot menu button.""" - scroll: WebElement = self.base.clickable_wrapper(self.pipette_calibration_overflow_2, 3) - actions = ActionChains(self.base.driver) - actions.move_to_element(scroll).perform() - return scroll - - def click_pipette_calibration_overflow_2(self) -> None: - """Click the pipette calibration overflow button one down.""" - # TODO add locators - self.base.click(self.pipette_calibration_overflow_2) - - calibrate_pipette_offset_button: Element = Element( - (By.XPATH, '//button[text()="Calibrate Pipette Offset"]'), - "Calibrate pipette offset button on the three dot menu.", - ) - - def click_pipette_offset_calibrate_button(self) -> None: - """Click the calibrate button.""" - scroll: WebElement = self.base.clickable_wrapper(self.calibrate_pipette_offset_button, 3) - actions = ActionChains(self.base.driver) - actions.move_to_element(scroll).perform() - self.base.click(self.calibrate_pipette_offset_button) - - # pipette calibration - - start_tip_length: Element = Element( - (By.XPATH, '//button[text()="start tip length calibration"]'), - "Start tip length calibration button.", - ) - - def click_start_tip_length(self) -> None: - """Click the start tip length calibration button.""" - self.base.click(self.start_tip_length) - - confirm_placement: Element = Element( - (By.XPATH, '//button[text()="Confirm placement and continue"]'), - "Confirm placement and continue button.", - ) - - def click_confirm_placement(self) -> None: - """Click the Confirm placement and continue button.""" - self.base.click(self.confirm_placement) - - down_button: Element = Element( - (By.XPATH, '//button[@title="down"]'), - "down button.", - ) - - def click_down(self) -> None: - """Click the down button.""" - self.base.click(self.down_button) - - back_button: Element = Element( - (By.XPATH, '//button[@title="back"]'), - "back button.", - ) - - def click_back(self) -> None: - """Click the back button.""" - self.base.click(self.back_button) - - save_nozzle_z: Element = Element( - (By.XPATH, '//button[text()="Save nozzle z-axis and move to pick up tip"]'), - "Save nozzle z button.", - ) - - def click_save_nozzle_z(self) -> None: - """Click the save nozzle z axis button.""" - self.base.click(self.save_nozzle_z) - - pickup_tip: Element = Element( - (By.XPATH, '//button[text()="Pick up tip"]'), - "Pickup tip button.", - ) - - def click_pickup_tip(self) -> None: - """Click pickup_tip button.""" - self.base.click(self.pickup_tip) - - yes_move_to_measure: Element = Element( - (By.XPATH, '//button[text()="Yes, move to measure tip length"]'), - "Yes_move_to_measure button.", - ) - - def click_yes_move_to_measure(self) -> None: - """Click yes move to measure button.""" - self.base.click(self.yes_move_to_measure) - - save_the_tip_length: Element = Element( - (By.XPATH, '//button[text()="Save the tip length"]'), - "Save the tip length button.", - ) - - def click_save_the_tip_length(self) -> None: - """Click Save the tip length button.""" - self.base.click(self.save_the_tip_length) - - continue_to_pipette_offset: Element = Element( - (By.XPATH, '//button[text()="continue to Pipette Offset Calibration"]'), - "Continue to pipette offset button.", - ) - - def click_continue_to_pipette_offset(self) -> None: - """Click Continue to pipette offset button.""" - # there can be 2 of these, seems UI bug, click the second one if there are 2 - self.base.clickable_wrapper(self.continue_to_pipette_offset, 3) - buttons: List[WebElement] = self.base.finds_wrapper(self.continue_to_pipette_offset) - self.base.click_webelement(buttons[-1]) - - def shift_down_arrow_key(self) -> None: - """Send the keystroke shift + down arrow key.""" - actions = ActionChains(self.base.driver) - actions.send_keys(Keys.LEFT_SHIFT + Keys.ARROW_DOWN) - actions.perform() - - save_calibration_move_to_slot_1: Element = Element( - (By.XPATH, '//button[text()="save calibration and move to slot 1"]'), - "Save_calibration_move_to_slot_1 button.", - ) - - def click_save_calibration_move_to_slot_1(self) -> None: - """Save_calibration_move_to_slot_1 button.""" - # there can be 2 of these, seems UI bug, click the second one if there are 2 - self.base.clickable_wrapper(self.save_calibration_move_to_slot_1, 3) - buttons: List[WebElement] = self.base.finds_wrapper(self.save_calibration_move_to_slot_1) - self.base.click_webelement(buttons[-1]) - - def up_arrow_key(self) -> None: - """Send the keystroke arrow up key.""" - actions = ActionChains(self.base.driver) - actions.send_keys(Keys.ARROW_UP) - actions.perform() - - save_calibration: Element = Element( - (By.XPATH, '//button[text()="save calibration"]'), - "Save_calibration button.", - ) - - def click_save_calibration(self) -> None: - """Save_calibration button.""" - # there can be 2 of these, seems UI bug, click the second one if there are 2 - self.base.clickable_wrapper(self.save_calibration, 3) - buttons: List[WebElement] = self.base.finds_wrapper(self.save_calibration) - self.base.click_webelement(buttons[-1]) - - return_tip_exit: Element = Element( - (By.XPATH, '//button[text()="Return tip to tip rack and exit"]'), - "Return_tip_exit button.", - ) - - def click_return_tip_exit(self) -> None: - """Return_tip_exit button.""" - # there can be 2 of these, seems UI bug, click the second one if there are 2 - self.base.clickable_wrapper(self.return_tip_exit, 3) - buttons: List[WebElement] = self.base.finds_wrapper(self.return_tip_exit) - self.base.click_webelement(buttons[-1]) - - pipette_offset_missing_banner: Element = Element( - (By.XPATH, '//div[text()="Pipette Offset calibration missing"]'), - "Return_tip_exit button.", - ) - - def invisible_pipette_offset_missing_banner_safely(self) -> Optional[WebElement]: - """Safely wait for Pipette Offset calibration missing banner to not be present or visible.""" - return self.base.invisible_wrapper_safe(self.pipette_offset_missing_banner, 15) - - calibrate_now = Element( - ( - By.XPATH, - "//button[text()='Calibrate now']", - ), - "Calibrate now buttons.", - ) - # tried this and on Windows was not found, present or clickable - # f"//div[@data-testid='PipetteCard_{pipette}']//button[text()='Calibrate now']" - - def wait_for_calibrate_now_present(self) -> None: - """Wait calibrate now button clickable.""" - self.base.clickable_wrapper(self.calibrate_now) - - def click_calibrate_now(self) -> None: - """Click the first calibrate now button on the screen.""" - self.base.click(self.calibrate_now) - - continue_with_calibration_block: Element = Element( - (By.XPATH, '//button[text()="Use Calibration Block"]'), - "Continue with calibration block button.", - ) - - def click_continue_with_calibration_block(self) -> None: - """Return_tip_exit button.""" - with_block = self.get_with_calibration_block_safe() - if with_block: - with_block.click() - - def get_with_calibration_block_safe(self) -> Optional[WebElement]: - """Look if a robot has a run by name. Return WebElement or None if not.""" - return self.base.clickable_wrapper_safe(self.continue_with_calibration_block, 4) diff --git a/app-testing/automation/pages/labware_landing.py b/app-testing/automation/pages/labware_landing.py deleted file mode 100644 index 2ed609f2881..00000000000 --- a/app-testing/automation/pages/labware_landing.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Model for the Labware Landing page that displays labware info for the app.""" - -from typing import Optional - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element - - -class LabwareLanding: - """Elements and actions for the Labware Landing Page that loads when the app is opened.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - def get_labware_image(self) -> WebElement: - """Get the labware image on the labware card.""" - header: Element = Element( - (By.ID, "LabwareCard_labwareImage"), - "the labware image on the labware card", - ) - return self.base.present_wrapper(header, 2) - - def get_labware_name(self) -> WebElement: - """Get the labware name on the labware card.""" - header: Element = Element( - (By.ID, "LabwareCard_labwareName"), - "the labware name on the labware card", - ) - return self.base.present_wrapper(header, 2) - - def get_api_name(self) -> WebElement: - """Get the labware api name on the labware card.""" - header: Element = Element( - (By.ID, "LabwareCard_apiName"), - "the labware api name on the labware card", - ) - return self.base.present_wrapper(header, 2) - - def get_overflow_menu(self) -> WebElement: - """Get the labware overflow menu on the labware card.""" - header: Element = Element( - (By.ID, "LabwareCard_overflowMenu"), - "the labware overflow menu on the labware card", - ) - return self.base.present_wrapper(header, 2) - - def get_labware_header(self) -> WebElement: - """Get the labware heading on the labware landing page.""" - header: Element = Element( - (By.TAG_NAME, "h1"), - "the labware heading on the labware landing page", - ) - return self.base.present_wrapper(header, 2) - - import_button: Element = Element( - (By.XPATH, "//button[text()='Import']"), - "the import button on the labware landing page", - ) - - def get_import_button(self) -> WebElement: - """Get the import button on the labware landing page.""" - return self.base.present_wrapper(self.import_button, 5) - - def click_import_button(self) -> None: - """Click on the import button to labware landing page to import a labware file.""" - self.base.click(self.import_button) - - import_custom_labware_definition_header: Element = Element( - ( - By.XPATH, - "//h2[@data-testid='Slideout_title_Import a Custom Labware Definition']", - ), - "Slideout_title_Import a Custom Labware Definition", - ) - - def get_import_custom_labware_definition_header(self) -> WebElement: - """Get the labware Slideout_title_Import a Custom Labware Definition.""" - return self.base.present_wrapper(self.import_custom_labware_definition_header, 2) - - choose_file_button: Element = Element( - (By.ID, "UploadInput_protocolUploadButton"), - "the choose file button on the labware slideout", - ) - - def get_choose_file_button(self) -> WebElement: - """Get the choose file button on the labware slideout.""" - return self.base.present_wrapper(self.choose_file_button, 3) - - drag_drop_file_button: Element = Element( - (By.XPATH, '//label[@data-testid="file_drop_zone"]'), - "the drag and drop file button on Protocol Page", - ) - - def get_drag_drop_file_button(self) -> WebElement: - """Get the drag and drop file area.""" - return self.base.present_wrapper(self.drag_drop_file_button, 2) - - def get_error_toast_message(self) -> WebElement: - """Get the error toast message after an invalid labware definition is uploaded.""" - header: Element = Element( - ( - By.XPATH, - "//p[contains(text(),'invalid_labware.json. Invalid labware definition')]", - ), - "the error toast message after an invalid labware definition is uploaded", - ) - return self.base.clickable_wrapper(header, 2) - - def get_success_toast_message(self, filename: str = "sample_labware.json") -> Optional[WebElement]: - """Get the success toast message after an invalid labware definition is uploaded.""" - header: Element = Element( - (By.XPATH, f"//p[contains(text(),'{filename} imported.')]"), - "the success toast message after an invalid labware definition is uploaded", - ) - return self.base.clickable_wrapper_safe(header, 2) - - def get_duplicate_error_toast_message(self, filename: str = "sample_labware.json") -> Optional[WebElement]: - """Get the duplicate error toast message after an invalid labware definition is uploaded.""" - header: Element = Element( - ( - By.XPATH, - f"//p[contains(text(),'{filename}. Duplicate labware definition')]", - ), - "the duplicate error toast message after an invalid labware definition is uploaded", - ) - return self.base.clickable_wrapper_safe(header, 2) - - open_labware_creator: Element = Element( - (By.LINK_TEXT, "Open Labware Creator"), - "Open labware creator link at bottom of page.", - ) - - def get_open_labware_creator(self) -> WebElement: - """Open labware creator link.""" - return self.base.clickable_wrapper(self.open_labware_creator, 2) diff --git a/app-testing/automation/pages/labware_position_check.py b/app-testing/automation/pages/labware_position_check.py deleted file mode 100644 index 1db45492a38..00000000000 --- a/app-testing/automation/pages/labware_position_check.py +++ /dev/null @@ -1,673 +0,0 @@ -"""Model for the screen of Labware Position Check.""" - -from typing import Optional - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element - - -class LabwarePositionCheck: - """Class for Labware Position Check.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - labware_setup_position_check_button: Element = Element( - ( - By.ID, - "LabwareSetup_checkLabwarePositionsButton", - ), - "", - ) - - introScreen_labware_position_check_overview: Element = Element( - ( - By.ID, - "IntroScreen_labware_position_check_overview", - ), - "", - ) - - begin_labware_position_text_locator: Element = Element( - ( - By.XPATH, - "//button[text()='begin labware position check, move to Slot 2']", - ), - "", - ) - - how_to_tell_pipette_is_centered_link: Element = Element( - ( - By.ID, - "StepDetailText_link", - ), - "", - ) - - reveal_all_jog_controls: Element = Element( - ( - By.ID, - "LabwarePositionCheckStepDetail_reveal_jog_controls", - ), - "", - ) - - back_jog_button: Element = Element( - ( - By.XPATH, - "//button[@title='back']", - ), - "", - ) - left_jog_button: Element = Element( - ( - By.XPATH, - "//button[@title='left']", - ), - "", - ) - right_jog_button: Element = Element( - ( - By.XPATH, - "//button[@title='right']", - ), - "", - ) - forward_jog_button: Element = Element( - ( - By.XPATH, - "//button[@title='forward']", - ), - "", - ) - up_jog_button: Element = Element( - ( - By.XPATH, - "//button[@title='up']", - ), - "", - ) - down_jog_button: Element = Element( - ( - By.XPATH, - "//button[@title='down']", - ), - "", - ) - - confirm_position_button_pickup_tip: Element = Element( - ( - By.XPATH, - "//button[text()='Confirm position, pick up tip']", - ), - "", - ) - - confirm_position_moveto_slot_2: Element = Element( - ( - By.XPATH, - "//button[text()='Confirm position, move to slot 2']", - ), - "", - ) - - confirm_position_moveto_slot_5: Element = Element( - ( - By.XPATH, - "//button[text()='Confirm position, move to slot 5']", - ), - "", - ) - - confirm_position_moveto_slot_6: Element = Element( - ( - By.XPATH, - "//button[text()='Confirm position, move to slot 6']", - ), - "", - ) - - confirm_position_returntip_slot_home: Element = Element( - ( - By.XPATH, - "//button[text()='Confirm position, return tip to Slot 2 and home']", - ), - "", - ) - - labware_position_check_complete: Element = Element( - ( - By.XPATH, - "//h3[text()='Labware Position Check Complete']", - ), - "", - ) - - deckmap_labware_check_complete: Element = Element( - ( - By.ID, - "LabwarePositionCheck_deckMap", - ), - "", - ) - - section_list_step0: Element = Element( - ( - By.ID, - "sectionList_step_0", - ), - "", - ) - - section_list_step1: Element = Element( - ( - By.ID, - "sectionList_step_1", - ), - "", - ) - - section_list_step2: Element = Element( - ( - By.ID, - "sectionList_step_2", - ), - "", - ) - - close_and_apply_labware_offset_data_button: Element = Element( - ( - By.ID, - "Lpc_summaryScreen_applyOffsetButton", - ), - "", - ) - - labware_success_toast: Element = Element( - ( - By.XPATH, - "//span[text()='Labware Position Check complete. 3 Labware Offsets created.']", - ), - "", - ) - - labware_offsetbox_slot_4: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_slot_4_offsetBox", - ), - "", - ) - - labware_display_name_slot_4: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_slot_4_displayName", - ), - "", - ) - - labware_slot_4_offset_x_text: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_4_x", - ), - "", - ) - - labware_slot_4_offset_x_value: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_4_xValue", - ), - "", - ) - - labware_slot_4_offset_y_text: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_4_y", - ), - "", - ) - - labware_slot_4_offset_y_value: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_4_yValue", - ), - "", - ) - labware_slot_4_offset_z_text: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_4_z", - ), - "", - ) - - labware_slot_4_offset_z_value: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_4_zValue", - ), - "", - ) - labware_display_name_slot_5: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_slot_5_displayName", - ), - "", - ) - - labware_slot_5_offset_x_text: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_5_x", - ), - "", - ) - - labware_slot_5_offset_x_value: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_5_xValue", - ), - "", - ) - - labware_slot_5_offset_y_text: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_5_y", - ), - "", - ) - - labware_slot_5_offset_y_value: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_5_yValue", - ), - "", - ) - labware_slot_5_offset_z_text: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_5_z", - ), - "", - ) - - labware_slot_5_offset_z_value: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_5_zValue", - ), - "", - ) - labware_display_name_slot_2: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_slot_2_displayName", - ), - "", - ) - - labware_slot_2_offset_x_text: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_2_x", - ), - "", - ) - - labware_slot_2_offset_x_value: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_2_xValue", - ), - "", - ) - - labware_slot_2_offset_y_text: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_2_y", - ), - "", - ) - - labware_slot_2_offset_y_value: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_2_yValue", - ), - "", - ) - labware_slot_2_offset_z_text: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_2_z", - ), - "", - ) - - labware_slot_2_offset_z_value: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_2_zValue", - ), - "", - ) - - labware_offsetbox_slot_2: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_slot_2_offsetBox", - ), - "", - ) - - labware_offsetbox_slot_5: Element = Element( - ( - By.ID, - "LabwareInfoOverlay_slot_5_offsetBox", - ), - "", - ) - - def get_labware_position_check_button(self) -> WebElement: - """Button to locate LPC button.""" - button = self.base.clickable_wrapper(self.labware_setup_position_check_button, 2) - actions = ActionChains(self.base.driver) - actions.move_to_element(button).perform() - return button - - def get_labware_success_toast(self) -> WebElement: - """Element to locate the success toast of LPC.""" - return self.base.clickable_wrapper(self.labware_success_toast, 2) - - def get_close_and_apply_labware_offset_data_button(self) -> WebElement: - """Button to close and apply labware oddset data on LPC flow.""" - return self.base.clickable_wrapper(self.close_and_apply_labware_offset_data_button, 2) - - def get_section_list_step2(self) -> WebElement: - """Element to locate the section list step2.""" - return self.base.clickable_wrapper(self.section_list_step2, 2) - - def get_section_list_step1(self) -> WebElement: - """Element to locate the section list step1.""" - return self.base.clickable_wrapper(self.section_list_step1, 2) - - def get_section_list_step0(self) -> WebElement: - """Element to locate the section list step0.""" - return self.base.clickable_wrapper(self.section_list_step0, 2) - - def get_deckmap_labware_check_complete(self) -> WebElement: - """Element to locate the deckmap.""" - return self.base.clickable_wrapper(self.deckmap_labware_check_complete, 2) - - def get_labware_position_check_complete(self) -> WebElement: - """Element to locate the LPC complete text.""" - return self.base.clickable_wrapper(self.labware_position_check_complete, 2) - - def click_labware_position_button(self) -> None: - """Click labware position button.""" - button = self.base.clickable_wrapper(self.labware_setup_position_check_button, 2) - actions = ActionChains(self.base.driver) - actions.move_to_element(button).perform() - self.base.click(self.labware_setup_position_check_button) - - def get_introScreen_labware_position_check_overview(self) -> WebElement: - """Element to locate into screen of LPC overview.""" - return self.base.clickable_wrapper(self.introScreen_labware_position_check_overview, 2) - - def get_begin_labware_position_check_button(self) -> WebElement: - """Element to locate begin LPC button.""" - return self.base.clickable_wrapper(self.begin_labware_position_text_locator, 2) - - def click_begin_labware_position_check_button(self) -> None: - """Click begin_labware_position_text.""" - self.base.click(self.begin_labware_position_text_locator) - - def get_how_to_tell_pipette_is_centered_link(self) -> WebElement: - """Locator for how to tell pipette is centered link.""" - return self.base.clickable_wrapper(self.how_to_tell_pipette_is_centered_link, 2) - - def click_how_to_tell_pipette_is_centered_link(self) -> None: - """Click is centered.""" - self.base.click(self.how_to_tell_pipette_is_centered_link) - - def get_reveal_all_jog_controls(self) -> WebElement: - """Locator for reveal all jog controls.""" - return self.base.clickable_wrapper(self.reveal_all_jog_controls, 2) - - def click_reveal_all_jog_controls(self) -> None: - """Click reveal all.""" - self.base.click(self.reveal_all_jog_controls) - - def get_back_jog_button(self) -> WebElement: - """Locator for back jog button.""" - return self.base.clickable_wrapper(self.back_jog_button, 2) - - def click_back_jog_button(self) -> None: - """Click back jog.""" - self.base.click(self.back_jog_button) - - def get_left_jog_button(self) -> WebElement: - """Locator for left jog button.""" - return self.base.clickable_wrapper(self.left_jog_button, 2) - - def click_left_jog_button(self) -> None: - """Click left jog.""" - self.base.click(self.left_jog_button) - - def get_right_jog_button(self) -> WebElement: - """Locator for right jog button.""" - return self.base.clickable_wrapper(self.right_jog_button, 2) - - def click_right_jog_button(self) -> None: - """Click right jog.""" - self.base.click(self.right_jog_button) - - def get_forward_jog_button(self) -> WebElement: - """Locator for forward jog button.""" - return self.base.clickable_wrapper(self.forward_jog_button) - - def click_forward_jog_button(self) -> None: - """Click forward jog.""" - self.base.click(self.forward_jog_button) - - def get_up_jog_button(self) -> WebElement: - """Locator for up jog button.""" - return self.base.clickable_wrapper(self.up_jog_button, 2) - - def click_up_jog_button(self) -> None: - """Click up jog.""" - self.base.click(self.up_jog_button) - - def get_down_jog_button(self) -> WebElement: - """Locator for down jog button.""" - return self.base.clickable_wrapper(self.down_jog_button, 2) - - def click_down_jog_button(self) -> None: - """Click down jog.""" - self.base.click(self.down_jog_button) - - def get_confirm_position_button_pickup_tip(self) -> WebElement: - """Locator for confirm position button pickup.""" - toggle: WebElement = self.base.clickable_wrapper(self.confirm_position_button_pickup_tip, 5) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - return toggle - - def click_confirm_position_button_pickup_tip(self) -> None: - """Click confirm and pick up tip.""" - self.get_confirm_position_button_pickup_tip() - self.base.click(self.confirm_position_button_pickup_tip) - - def get_confirm_position_moveto_slot_2(self) -> WebElement: - """Locator for confirm position moveto slot.""" - toggle: WebElement = self.base.clickable_wrapper(self.confirm_position_moveto_slot_2, 5) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - return toggle - - def click_confirm_position_moveto_slot_2(self) -> None: - """Click confirm position move to slot 2.""" - self.get_confirm_position_moveto_slot_2() - self.base.click(self.confirm_position_moveto_slot_2) - - def get_confirm_position_moveto_slot_5(self) -> WebElement: - """Locator for confirm position move to slot.""" - toggle: WebElement = self.base.clickable_wrapper(self.confirm_position_moveto_slot_5, 5) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - return toggle - - def click_confirm_position_moveto_slot_5(self) -> None: - """Click confirm position move to slot 5.""" - self.get_confirm_position_moveto_slot_5() - self.base.click(self.confirm_position_moveto_slot_5) - - def get_confirm_position_moveto_slot_6(self) -> WebElement: - """Locator for confirm position moveto slot.""" - toggle: WebElement = self.base.clickable_wrapper(self.confirm_position_moveto_slot_6, 5) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - return toggle - - def click_confirm_position_moveto_slot_6(self) -> None: - """Click confirm position move to slot 6.""" - self.get_confirm_position_moveto_slot_6() - self.base.click(self.confirm_position_moveto_slot_6) - - def get_confirm_position_returntip_slot_home(self) -> WebElement: - """Locator for confirm position return tip .""" - return self.base.clickable_wrapper(self.confirm_position_returntip_slot_home, 2) - - def click_confirm_position_returntip_slot_home(self) -> None: - """Click confirm position return tip slot home.""" - self.base.click(self.confirm_position_returntip_slot_home) - - def click_get_close_and_apply_labware_offset_data_button(self) -> None: - """Click get close and apply.""" - self.base.click(self.close_and_apply_labware_offset_data_button) - - def get_labware_display_name_slot_4(self) -> WebElement: - """Labware name on deckmap.""" - return self.base.clickable_wrapper(self.labware_display_name_slot_4, 2) - - def get_labware_offsetbox_slot_4(self) -> WebElement: - """Labware offset box on deckmap.""" - return self.base.clickable_wrapper(self.labware_offsetbox_slot_4, 2) - - def get_labware_slot_4_offset_x_text(self) -> WebElement: - """Labware x text on slot 4 on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_4_offset_x_text, 2) - - def get_labware_slot_4_offset_x_value(self) -> WebElement: - """Labware x offset value on slot 4 on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_4_offset_x_value, 2) - - def get_labware_slot_4_offset_y_text(self) -> WebElement: - """Labware y osset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_4_offset_y_text, 2) - - def get_labware_slot_4_offset_y_value(self) -> WebElement: - """Labware y offset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_4_offset_y_value, 2) - - def get_labware_slot_4_offset_z_text(self) -> WebElement: - """Labware y osset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_4_offset_z_text, 2) - - def get_labware_slot_4_offset_z_value(self) -> WebElement: - """Labware y offset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_4_offset_z_value, 2) - - def get_labware_display_name_slot_5(self) -> WebElement: - """Labware name on deckmap.""" - return self.base.clickable_wrapper(self.labware_display_name_slot_5, 2) - - def get_labware_offsetbox_slot_5(self) -> WebElement: - """Labware offset box on deckmap.""" - return self.base.clickable_wrapper(self.labware_offsetbox_slot_5, 2) - - def get_labware_slot_5_offset_x_text(self) -> WebElement: - """Labware x text on slot 4 on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_5_offset_x_text, 2) - - def get_labware_slot_5_offset_x_value(self) -> WebElement: - """Labware x offset value on slot 4 on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_5_offset_x_value, 2) - - def get_labware_slot_5_offset_y_text(self) -> WebElement: - """Labware y offset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_5_offset_y_text, 2) - - def get_labware_slot_5_offset_y_value(self) -> WebElement: - """Labware y offset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_5_offset_y_value, 2) - - def get_labware_slot_5_offset_z_text(self) -> WebElement: - """Labware y osset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_5_offset_z_text, 2) - - def get_labware_slot_5_offset_z_value(self) -> WebElement: - """Labware y offset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_5_offset_z_value, 2) - - def get_labware_display_name_slot_2(self) -> WebElement: - """Labware name on deckmap.""" - return self.base.clickable_wrapper(self.labware_display_name_slot_2, 2) - - def get_labware_offsetbox_slot_2(self) -> WebElement: - """Labware offset box on deckmap.""" - return self.base.clickable_wrapper(self.labware_offsetbox_slot_2, 2) - - def get_labware_slot_2_offset_x_text(self) -> WebElement: - """Labware x text on slot 4 on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_2_offset_x_text, 2) - - def get_labware_slot_2_offset_x_value(self) -> WebElement: - """Labware x offset value on slot 4 on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_2_offset_x_value, 2) - - def get_labware_slot_2_offset_y_text(self) -> WebElement: - """Labware y osset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_2_offset_y_text, 2) - - def get_labware_slot_2_offset_y_value(self) -> WebElement: - """Labware y offset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_2_offset_y_value, 2) - - def get_labware_slot_2_offset_z_text(self) -> WebElement: - """Labware y osset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_2_offset_z_text, 2) - - def get_labware_slot_2_offset_z_value(self) -> WebElement: - """Labware y offset value on deckmap.""" - return self.base.clickable_wrapper(self.labware_slot_2_offset_z_value, 2) - - ignore_stored_data_button: Element = Element( - (By.XPATH, "//button[text()='Ignore stored data']"), - "ignore stored data button on modal for LPC enhancement", - ) - - def get_ignored_stored_data(self) -> Optional[WebElement]: - """Safely get the ignore stored data button.""" - return self.base.clickable_wrapper_safe(self.ignore_stored_data_button, 4) - - def click_ignored_stored_data(self) -> None: - """Click the ignore stored data button.""" - self.base.click(self.ignore_stored_data_button) diff --git a/app-testing/automation/pages/labware_setup.py b/app-testing/automation/pages/labware_setup.py deleted file mode 100644 index 818a41a3518..00000000000 --- a/app-testing/automation/pages/labware_setup.py +++ /dev/null @@ -1,196 +0,0 @@ -"""Model for the screen of Labware Setup.""" - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element - - -class LabwareSetup: - """Labware setup on setup for run.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - labware_setup_text_locator: Element = Element( - ( - By.ID, - "CollapsibleStep_Labware Setup", - ), - "todo description", - ) - securing_labware_to_magnetic_module_link: Element = Element( - ( - By.ID, - "ExtraAttentionWarning_magnetic_module_link", - ), - "todo description", - ) - securing_labware_to_thermocycler_link: Element = Element( - ( - By.ID, - "ExtraAttentionWarning_thermocycler_link", - ), - "todo description", - ) - magnetic_module_modal: Element = Element( - ( - By.XPATH, - "//h3[text()='How To Secure Labware to the Magnetic Module']", - ), - "todo description", - ) - thermocycler_module_modal: Element = Element( - ( - By.XPATH, - "//h3[text()='How To Secure Labware to the Thermocycler']", - ), - "todo description", - ) - close_button: Element = Element( - ( - By.XPATH, - '//div[contains(normalize-space(@class), "modals")]//button[text()="close"]', - ), - "todo description", - ) - proceed_to_run_button: Element = Element( - (By.ID, "LabwareSetup_proceedToRunButton"), - "todo description", - ) - - start_run_button: Element = Element( - ( - By.XPATH, - "//p[text()='Start Run']", - ), - "todo description", - ) - - run_again_button: Element = Element( - ( - By.XPATH, - "//p[text()='Run Again']", - ), - "todo description", - ) - - protocol_complete_banner: Element = Element( - ( - By.XPATH, - "//span[text()='Protocol run complete']", - ), - "todo description", - ) - - close_protocol_text_locator: Element = Element( - ( - By.XPATH, - "//button[text()='close']", - ), - "todo description", - ) - yes_close_now_text_locator: Element = Element( - ( - By.XPATH, - "//button[text()='Yes, close now']", - ), - "todo description", - ) - - def get_labware_setup_text(self) -> WebElement: - """Locator for labware setup text.""" - return self.base.clickable_wrapper(LabwareSetup.labware_setup_text_locator) - - def get_magnetic_module_link(self) -> WebElement: - """Locator for securing labware to magentic module link.""" - return self.base.clickable_wrapper(LabwareSetup.securing_labware_to_magnetic_module_link) - - def click_magnetic_module_link(self) -> None: - """Click magnetic module link.""" - self.base.click(LabwareSetup.securing_labware_to_magnetic_module_link) - - def get_thermocycler_link(self) -> WebElement: - """Locator for securing labware to thermocycler module link.""" - return self.base.clickable_wrapper(LabwareSetup.securing_labware_to_thermocycler_link) - - def click_thermocycler_module_link(self) -> None: - """Click thermocycler module link.""" - self.base.click(LabwareSetup.securing_labware_to_thermocycler_link) - - def get_magnetic_module_modal_text(self) -> WebElement: - """Locator for magnetic module modal.""" - return self.base.clickable_wrapper(LabwareSetup.magnetic_module_modal) - - def get_thermocycler_module_modal_text(self) -> WebElement: - """Locator for thermocycler module modal.""" - return self.base.clickable_wrapper(LabwareSetup.thermocycler_module_modal) - - def get_close_button(self) -> WebElement: - """Locator for close button.""" - toggle: WebElement = self.base.clickable_wrapper(LabwareSetup.close_button) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - return toggle - - def click_close_button(self) -> None: - """Click close button.""" - toggle: WebElement = self.base.clickable_wrapper(LabwareSetup.close_button) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - self.base.click(LabwareSetup.close_button) - - def get_proceed_to_run_button(self) -> WebElement: - """Locator for proceed to run button.""" - scroll: WebElement = self.base.clickable_wrapper(LabwareSetup.proceed_to_run_button) - actions = ActionChains(self.base.driver) - actions.move_to_element(scroll).perform() - return scroll - - def click_proceed_to_run_button(self) -> None: - """Click proceed to run.""" - scroll: WebElement = self.base.clickable_wrapper(LabwareSetup.proceed_to_run_button) - actions = ActionChains(self.base.driver) - actions.move_to_element(scroll).perform() - self.base.click(LabwareSetup.proceed_to_run_button) - - def get_start_run_button(self) -> WebElement: - """Locator for start run button.""" - return self.base.clickable_wrapper(LabwareSetup.start_run_button) - - def click_start_run_button(self) -> None: - """Click start run.""" - self.base.click(LabwareSetup.start_run_button) - - def get_run_again_button(self) -> WebElement: - """Locator for run again button.""" - return self.base.clickable_wrapper(LabwareSetup.run_again_button) - - def get_protocol_complete_banner(self) -> WebElement: - """Locator for protocol complete banner.""" - return self.base.clickable_wrapper(LabwareSetup.protocol_complete_banner) - - def get_protocol_close_button(self) -> WebElement: - """Locator for protocol close button.""" - return self.base.clickable_wrapper(LabwareSetup.close_protocol_text_locator) - - def get_confirmation_close_button(self) -> WebElement: - """Locator for yes close now button.""" - return self.base.clickable_wrapper(LabwareSetup.yes_close_now_text_locator) - - def click_protocol_close_button(self) -> None: - """Click protocol close.""" - self.base.click(LabwareSetup.close_protocol_text_locator) - - def click_confirmation_close_button(self) -> None: - """Click confirmation close.""" - self.base.click(LabwareSetup.yes_close_now_text_locator) - - def click_labware_setup_text(self) -> None: - """Click labware setup text.""" - self.base.click(LabwareSetup.labware_setup_text_locator) diff --git a/app-testing/automation/pages/modal.py b/app-testing/automation/pages/modal.py deleted file mode 100644 index 8cdcf9811a4..00000000000 --- a/app-testing/automation/pages/modal.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Model for the App page that displays info and settings for the app.""" - -from typing import Optional - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element - - -class Modal: - """Elements and actions for the Page that loads when the app is opened.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - not_now: Element = Element((By.XPATH, '//button[text()="Not Now"]'), "Not Now to upgrade.") - - def get_not_now(self) -> Optional[WebElement]: - """Safely get the not now button.""" - return self.base.clickable_wrapper_safe(self.not_now, 3) diff --git a/app-testing/automation/pages/module_setup.py b/app-testing/automation/pages/module_setup.py deleted file mode 100644 index 7c7656bc710..00000000000 --- a/app-testing/automation/pages/module_setup.py +++ /dev/null @@ -1,105 +0,0 @@ -"""Model for the screen of module setup.""" - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element - - -class ModuleSetup: - """All elements and actions for the Module Setup.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - proceed_to_module_setup: Element = Element((By.ID, "RobotCalStep_proceedButton"), "proceed to module setup button") - - module_setup_text_locator: Element = Element( - ( - By.ID, - "CollapsibleStep_Module Setup", - ), - "setup text", - ) - - thermocycler_module: Element = Element( - ( - By.XPATH, - "//p[text()='Thermocycler Module GEN1']", - ), - "", - ) - - magnetic_module: Element = Element((By.XPATH, "//p[text()='Magnetic Module GEN2']"), "") - - temperature_module: Element = Element( - ( - By.XPATH, - "//p[text()='Temperature Module GEN2']", - ), - "", - ) - - proceed_to_labware_setup: Element = Element( - ( - By.ID, - "ModuleSetup_proceedToLabwareSetup", - ), - "", - ) - - def get_proceed_to_module_setup(self) -> WebElement: - """Locator for proceed to module setup.""" - return self.base.clickable_wrapper(ModuleSetup.proceed_to_module_setup) - - def get_thermocycler_module(self) -> WebElement: - """Locator for thermocycler module on deckmap.""" - return self.base.clickable_wrapper(ModuleSetup.thermocycler_module) - - def get_temperature_module(self) -> WebElement: - """Locator for temperature module on deckmap.""" - return self.base.clickable_wrapper(ModuleSetup.temperature_module) - - def get_module_setup_text_locator(self) -> WebElement: - """Locator for module setup text.""" - toggle: WebElement = self.base.clickable_wrapper(ModuleSetup.module_setup_text_locator) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - return toggle - - def get_magnetic_module(self) -> WebElement: - """Locator for magnetic module on deckmap.""" - toggle: WebElement = self.base.clickable_wrapper(ModuleSetup.magnetic_module) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - return toggle - - def get_proceed_to_labware_setup(self) -> WebElement: - """Locator for proceed to labware setup.""" - toggle: WebElement = self.base.clickable_wrapper(ModuleSetup.proceed_to_labware_setup) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - return toggle - - def click_proceed_to_labware_setup(self) -> None: - """Proceed to labware setup.""" - toggle: WebElement = self.base.clickable_wrapper(ModuleSetup.proceed_to_labware_setup) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - self.base.click(ModuleSetup.proceed_to_labware_setup) - - def click_proceed_to_module_setup(self) -> None: - """Click proceed to labware setup.""" - self.base.click(ModuleSetup.proceed_to_module_setup) - - def click_module_setup_text(self) -> None: - """Click module setup text.""" - toggle: WebElement = self.base.clickable_wrapper(ModuleSetup.module_setup_text_locator) - actions = ActionChains(self.base.driver) - actions.move_to_element(toggle).perform() - self.base.click(ModuleSetup.module_setup_text_locator) diff --git a/app-testing/automation/pages/protocol_landing.py b/app-testing/automation/pages/protocol_landing.py deleted file mode 100644 index 224043f6234..00000000000 --- a/app-testing/automation/pages/protocol_landing.py +++ /dev/null @@ -1,584 +0,0 @@ -"""Model for the screens of protocol upload v5dot1.""" - -from typing import Optional - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element -from automation.driver.wait import wait_until_none - - -class ProtocolLanding: - """Class for Protocol landing page.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - def get_choose_file_button(self) -> WebElement: - """Get the choose file button on Protocol Page.""" - header: Element = Element( - (By.ID, "UploadInput_protocolUploadButton"), - "the choose file button on Protocol Page", - ) - return self.base.clickable_wrapper(header, 5) - - def get_drag_drop_file_button(self) -> WebElement: - """Get the drag and drop file button on Protocol Page.""" - header: Element = Element( - (By.XPATH, '//label[@data-testid="file_drop_zone"]'), - "the drag and drop file button on Protocol Page", - ) - return self.base.present_wrapper(header, 4) - - def get_protocol_library_link(self) -> WebElement: - """Get the Protocol Library Link on Protocol Page.""" - header: Element = Element( - (By.ID, "EmptyStateLinks_protocolLibraryButton"), - "the Protocol Library Link on Protocol Page.", - ) - return self.base.present_wrapper(header, 2) - - def get_protocol_designer_link(self) -> WebElement: - """Get the Protocol Designer Link on Protocol Page.""" - header: Element = Element( - (By.ID, "EmptyStateLinks_protocolDesignerButton"), - "the Protocol Designer Link on Protocol Page.", - ) - return self.base.present_wrapper(header, 2) - - def get_python_api_link(self) -> WebElement: - """Get the python api Link on Protocol Page.""" - header: Element = Element( - (By.ID, "EmptyStateLinks_apiDocsButton"), - "the python api Link on Protocol Page.", - ) - return self.base.present_wrapper(header, 2) - - def get_show_in_folder(self) -> WebElement: - """Get show in folder in overflow button on Protocol Page.""" - header: Element = Element( - (By.XPATH, "//button[@data-testid='ProtocolOverflowMenu_showInFolder']"), - "show in folder in overflow button on Protocol Page.", - ) - return self.base.present_wrapper(header, 2) - - def get_run_protocol(self) -> WebElement: - """Get run in overflow button on Protocol Page.""" - header: Element = Element( - (By.XPATH, "//button[@data-testid='ProtocolOverflowMenu_run']"), - "run in overflow button on Protocol Page.", - ) - return self.base.present_wrapper(header, 2) - - def get_delete_protocol(self) -> WebElement: - """Get delete protocol in overflow button on Protocol Page.""" - header: Element = Element( - (By.XPATH, "//button[@data-testid='ProtocolOverflowMenu_deleteProtocol']"), - "delete protocol in overflow button on Protocol Page.", - ) - return self.base.present_wrapper(header, 2) - - def get_delete_protocol_confirm(self) -> WebElement: - """Get delete protocol in overflow button on popout.""" - header: Element = Element( - (By.XPATH, '//button[text()="Yes, delete protocol"]'), - "delete protocol confirm button.", - ) - return self.base.clickable_wrapper(header, 3) - - def get_import_button_protocol_landing(self) -> WebElement: - """Get the import button on Protocol Landing Page.""" - header: Element = Element( - (By.XPATH, '//button[text()="Import"]'), - "the import button on Protocol Landing Page.", - ) - return self.base.present_wrapper(header, 4) - - def get_deckMap_protocol_landing(self, protocol_name: str) -> WebElement: - """Get the deckmap on Protocol Landing Page.""" - header: Element = Element( - ( - By.XPATH, - f"//div[@data-testid='ProtocolCard_deckLayout_{protocol_name}']", - ), - "the deckmap on Protocol Landing Page.", - ) - return self.base.clickable_wrapper(header, 7) - - def get_protocol_name_text_protocol_landing(self, protocol_name: str) -> str: - """Get the protocol name on Protocol Landing Page.""" - header: Element = Element( - ( - By.XPATH, - f'//h3[@data-testid="ProtocolCard_{protocol_name}"]', - ), - "the protocol name on Protocol Landing Page.", - ) - element = self.base.clickable_wrapper(header, 4) - if not element: - return "" - return element.text - - def get_left_mount_text_protocol_landing(self) -> str: - """Get the left mount on Protocol Landing Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolCard_leftMount_script_pur_sample_1"]//h6', - ), - "the left mount on Protocol Landing Page.", - ) - element = self.base.present_wrapper(header, 4) - if not element: - return "" - return element.text - - def get_left_mount_value_protocol_landing(self) -> str: - """Get the left mount value on Protocol Landing Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolCard_leftMount_script_pur_sample_1"]//p', - ), - "the left mount value on Protocol Landing Page.", - ) - return self.base.present_wrapper(header, 1).text - - def get_right_mount_text_protocol_landing(self) -> str: - """Get the right mount on Protocol Landing Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolCard_rightMount_script_pur_sample_1"]//h6', - ), - "the right mount on Protocol Landing Page.", - ) - return self.base.present_wrapper(header, 1).text - - def get_right_mount_value_protocol_landing(self) -> str: - """Get the right mount value on Protocol Landing Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolCard_rightMount_script_pur_sample_1"]//p', - ), - "the right mount value on Protocol Landing Page.", - ) - return self.base.present_wrapper(header, 1).text - - def get_mag_module_protocol_landing(self) -> WebElement: - """Get the mag module on Protocol Landing Page.""" - header: Element = Element( - (By.XPATH, '//*[@data-testid="ModuleIcon_ot-magnet-v2"]'), - "the mag module on Protocol Landing Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_temp_module_protocol_landing(self) -> WebElement: - """Get the temp module on Protocol Landing Page.""" - header: Element = Element( - (By.XPATH, '//*[@data-testid="ModuleIcon_ot-temperature-v2"]'), - "the temp module on Protocol Landing Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_thermocycler_module_protocol_landing(self) -> WebElement: - """Get the thermocycler module on Protocol Landing Page.""" - header: Element = Element( - (By.XPATH, '//*[@data-testid="ModuleIcon_ot-thermocycler"]'), - "the thermocycler module on Protocol Landing Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_updated_timestamp_protocol_landing(self) -> WebElement: - """Get the updated timestamp module on Protocol Landing Page.""" - header: Element = Element( - (By.XPATH, '//div[@data-testid="ProtocolCard_date_script_pur_sample_1"]'), - "the updated timestamp on Protocol Landing Page.", - ) - return self.base.present_wrapper(header, 1) - - def click_protocol_card(self) -> None: - """Get the protocol card.""" - card: Element = Element( - (By.XPATH, '//h3[@data-testid="ProtocolCard_script_pur_sample_1"]'), - "protocol card", - ) - self.base.click(card) - - def protocol_list_parents(self) -> Element: - """The parent row div on the protocol landing page.""" - return Element( - (By.XPATH, '//div[contains(@class, "ProtocolCard___StyledBox")]'), - "protocol parent rows", - ) - - def get_protocol_list_parents(self) -> list[WebElement]: - return self.base.finds_wrapper(self.protocol_list_parents()) - - def delete_all_protocols(self) -> None: - """Delete every protocol.""" - for _protocol in self.get_protocol_list_parents(): - self.click_overflow_menu() - self.base.click_webelement(self.get_delete_protocol()) - self.base.click_webelement(self.get_delete_protocol_confirm()) - - def click_overflow_menu(self) -> None: - """Get the overflow menu on protocol landing page.""" - card: Element = Element( - ( - By.XPATH, - '//button[@data-testid="ProtocolOverflowMenu_overflowBtn"]', - ), - "protocol card", - ) - self.base.click(card) - - def get_creation_method_text_protocol_detail(self) -> str: - """Get the creation method text on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_creationMethod"]//h6', - ), - "the creation method text on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 2).text - - def get_creation_method_value_protocol_detail(self) -> str: - """Get the creation method text on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_creationMethod"]//p', - ), - "the creation method text on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1).text - - def get_last_updated_text_protocol_detail(self) -> str: - """Get the last updated text on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_lastUpdated"]//h6', - ), - "the last updated text on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1).text - - def get_last_updated_value_protocol_detail(self) -> WebElement: - """Get the last updated value on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_lastUpdated"]//p', - ), - "the last update value on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_last_analyzed_text_protocol_detail(self) -> str: - """Get the last analyzed text on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_lastAnalyzed"]//h6', - ), - "the last analyzed text on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1).text - - def get_last_analyzed_value_protocol_detail(self) -> WebElement: - """Get the last analyzed value on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_lastAnalyzed"]//p', - ), - "the last analyzed value on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_author_text_protocol_detail(self) -> str: - """Get the author text on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_author"]//h6', - ), - "the author text on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 2).text - - def get_author_value_protocol_detail(self) -> str: - """Get the author text on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_author"]//p', - ), - "the author text on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1).text - - def get_description_text_protocol_detail(self) -> WebElement: - """Get the description text on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_description"]//p', - ), - "the description text on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_deckmap_protocol_detail(self) -> WebElement: - """Get the deckmap on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//*[@data-testid="ProtocolDetails_deckMap"]', - ), - "the deckmap on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_robot_configuration_protocol_detail(self) -> str: - """Get the robot configuration text on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//*[@data-testid="ProtocolDetails_robotConfig"]//p', - ), - "the robot configuration text on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1).text - - def get_labware_tab_protocol_detail(self) -> str: - """Get the labware text on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//div[@data-testid="ProtocolDetails_labware"]//p', - ), - "the labware text on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1).text - - def get_left_mount_protocol_detail(self) -> WebElement: - """Get the left mount on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//*[@data-testid="RobotConfigurationDetails_left mount"]', - ), - "the left mount on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_right_mount_protocol_detail(self) -> WebElement: - """Get the right mount on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//*[@data-testid="RobotConfigurationDetails_right mount"]', - ), - "the right mount on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_mag_mod_protocol_detail(self) -> WebElement: - """Get the mag module on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//*[@data-testid="ModuleIcon_ot-magnet-v2"]', - ), - "the mag module on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_temp_mod_protocol_detail(self) -> WebElement: - """Get the temp module on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//*[@data-testid="ModuleIcon_ot-temperature-v2"]', - ), - "the temp module on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def get_close_button_uncurrent_run(self) -> WebElement: - """Get the close button on success banner Page.""" - header: Element = Element( - ( - By.XPATH, - '//*[@data-testid="Banner_close-button"]', - ), - "the close button on success banner.", - ) - return self.base.present_wrapper(header, 1) - - def get_thermocycler_mod_protocol_detail(self) -> WebElement: - """Get the thermocycler module on Protocol Detail Page.""" - header: Element = Element( - ( - By.XPATH, - '//*[@data-testid="ModuleIcon_ot-thermocycler"]', - ), - "the thermocycler module on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def click_run_protocol_on_protocol_detail(self) -> None: - """Get the run protocol on Protocol Detail Page.""" - button: Element = Element( - ( - By.XPATH, - '//button[@data-testid="ProtocolDetails_runProtocol"]', - ), - "the run protocol on Protocol Detail Page.", - ) - self.base.click(button) - - def click_run_on_protocol_landing(self) -> None: - """Get the run protocol on Protocol Landing Page.""" - button: Element = Element( - ( - By.XPATH, - '//button[@data-testid="ProtocolOverflowMenu_run"]', - ), - "the run protocol on Protocol Landing Page.", - ) - self.base.click(button) - - def get_slideout_header_on_protocol_detail(self) -> WebElement: - """Get the slideout header on Protocol Detail Page.""" - header: Element = Element( - ( - By.TAG_NAME, - "h2", - ), - "the slideout header on Protocol Detail Page.", - ) - return self.base.present_wrapper(header, 1) - - def click_proceed_to_setup_on_protocol_detail(self) -> None: - """Get the proceed to setup on Protocol Detail Page.""" - button: Element = Element( - ( - By.XPATH, - '//button[text()="Proceed to setup"]', - ), - "the proceed to setup on Protocol Detail Page.", - ) - self.base.click(button) - - def click_robot_on_protocol_detail(self) -> None: - """Protocol Detail Page robot.""" - button: Element = Element( - ( - By.TAG_NAME, - "h6", - ), - "the robot on Protocol Detail Page.", - ) - self.base.click(button) - - def click_close_button_uncurrent_run(self) -> None: - """Click close button.""" - button: Element = Element( - ( - By.XPATH, - '//*[@data-testid="Banner_close-button"]', - ), - "click close button.", - ) - self.base.click(button) - - def loading_data(self) -> Element: - """Element locator for the spinner that appears when a protocol is analyzing.""" - return Element( - ( - By.XPATH, - '//p[text()="Loading data..."]', - ), - "Loading data... that appears when a protocol is analyzing", - ) - - def get_loading_data_safe(self) -> Optional[WebElement]: - """Get loading data.""" - ptext: Optional[WebElement] = self.base.present_wrapper_safe(self.loading_data(), 1) - if ptext is None: - self.console.print("Loading data... NOT found!", style="white on blue") - else: - self.console.print("Loading data... found!", style="white on blue") - return ptext - - def wait_until_loading_data_gone(self, timeout_sec: int = 61) -> bool: - """Wait for loading data to go away.""" - return wait_until_none(self.get_loading_data_safe, timeout_sec=timeout_sec) - - def error_details(self) -> Element: - """Element locator for the link to the protocol analysis error details.""" - return Element( - ( - By.XPATH, - '//a[text()="error details"]', - ), - "error details button", - ) - - def get_error_details_safe(self) -> Optional[WebElement]: - """Get error details link.""" - error_details: Optional[WebElement] = self.base.present_wrapper_safe(self.error_details(), 2) - if error_details is None: - self.console.print("No errors in the protocol analysis.", style="bright_yellow on blue") - else: - self.console.print("Errors in protocol analysis.", style="bright_yellow on blue") - return error_details - - def reanalyze_link(self) -> Element: - """Element locator for the reanalyze link when there is an error.""" - return Element( - ( - By.XPATH, - '//a[text()="Reanalyze"]', - ), - "Reanalyze button", - ) - - def popout_error_p(self) -> Element: - """Element locator for the analysis error details.""" - return Element( - ( - By.XPATH, - "//div[@type='error']//p", - ), - "popout analysis error text", - ) - - def get_popout_error(self) -> WebElement: - """Get the popout error element.""" - return self.base.present_wrapper(self.popout_error_p()) - - def popout_close(self) -> Element: - """Popout close button.""" - return Element( - (By.XPATH, "//button[text()='close']"), - "close button", - ) - - def click_popout_close(self) -> None: - """Click the popout close button.""" - self.base.click(self.popout_close()) - - -# data-testid="ModalHeader_icon_close_Protocol analysis failure." diff --git a/app-testing/automation/pages/setup_calibration.py b/app-testing/automation/pages/setup_calibration.py deleted file mode 100644 index ea9589f027f..00000000000 --- a/app-testing/automation/pages/setup_calibration.py +++ /dev/null @@ -1,147 +0,0 @@ -"""Model for the screen of robot calibration.""" - -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement - -from automation.driver.base import Base, Element - -""" Class for Setup For Run Page.""" - - -class SetupCalibration: - """Setup for run calibration section.""" - - def __init__(self, driver: WebDriver, console: Console, execution_id: str) -> None: - """Initialize with driver.""" - self.base: Base = Base(driver, console, execution_id) - self.console: Console = console - - setup_for_run_text_locator: Element = Element( - ( - By.ID, - "RunSetupCard_setupForRun", - ), - "Setup for run text", - ) - - robot_calibration_text_locator: Element = Element( - ( - By.ID, - "CollapsibleStep_Robot Calibration", - ), - "Robot Calibration text.", - ) - - deck_calibration_text_locator: Element = Element( - ( - By.ID, - "DeckCalibration_deckCalibrationTitle", - ), - "Deck calibration text.", - ) - - required_pipettes_text_locator: Element = Element( - ( - By.ID, - "PipetteCalibration_requiredPipettesTitle", - ), - "required pipettes text", - ) - - required_tip_length_calibration_text_locator: Element = Element( - ( - By.ID, - "TipRackCalibration_requiredTipLengthTitle", - ), - "tip length calibration text", - ) - - calibration_helper_icon_text_locator: Element = Element( - ( - By.ID, - "RunSetupCard_calibrationText", - ), - "calibration helper icon text", - ) - - robot_calibration_help_link_locator: Element = Element( - ( - By.ID, - "DeckCalibration_robotCalibrationHelpLink", - ), - "calibration help link locator", - ) - - robot_calibration_help_model_text: Element = Element( - ( - By.XPATH, - "//p[text()='See How Robot Calibration Works']", - ), - "calibration help modal text", - ) - close_robot_calibration_button: Element = Element( - ( - By.ID, - "RobotCalModal_closeButton", - ), - "close robot calibration button", - ) - - proceed_to_module_setup_cta: Element = Element((By.ID, "RobotCalStep_proceedButton"), "proceed to module setup button") - - def get_setup_for_run(self) -> WebElement: - """Search for the setup for run text.""" - return self.base.clickable_wrapper(SetupCalibration.setup_for_run_text_locator) - - def get_robot_calibration(self) -> WebElement: - """Search for the robot calibration text.""" - return self.base.clickable_wrapper(SetupCalibration.robot_calibration_text_locator) - - def get_deck_calibration(self) -> WebElement: - """Search for the deck calibration text.""" - return self.base.clickable_wrapper(SetupCalibration.deck_calibration_text_locator) - - def get_required_pipettes(self) -> WebElement: - """Search for the required pipette calibration.""" - return self.base.clickable_wrapper(SetupCalibration.required_pipettes_text_locator) - - def click_robot_calibration(self) -> None: - """Click robot calibration.""" - self.base.click(SetupCalibration.robot_calibration_text_locator) - - def get_calibration_ready_locator(self) -> WebElement: - """Calibration helper icon text.""" - return self.base.clickable_wrapper(SetupCalibration.calibration_helper_icon_text_locator) - - def get_robot_calibration_help_locator(self) -> WebElement: - """Robot calibration help link.""" - return self.base.clickable_wrapper(SetupCalibration.robot_calibration_help_link_locator) - - def get_robot_calibration_help_modal_text(self) -> WebElement: - """Robot calibration help modal.""" - return self.base.clickable_wrapper(SetupCalibration.robot_calibration_help_model_text) - - def get_robot_calibration_close_button(self) -> WebElement: - """Robot claibration close button.""" - close: WebElement = self.base.clickable_wrapper(SetupCalibration.close_robot_calibration_button) - actions = ActionChains(self.base.driver) - actions.move_to_element(close).perform() - return close - - def click_robot_calibration_help_link(self) -> None: - """Robot calibration help link.""" - self.base.click(SetupCalibration.robot_calibration_help_link_locator) - - def click_robot_calibration_close_button(self) -> None: - """Click robot calibration close.""" - close: WebElement = self.base.clickable_wrapper(SetupCalibration.close_robot_calibration_button) - actions = ActionChains(self.base.driver) - actions.move_to_element(close).perform() - self.base.click(SetupCalibration.close_robot_calibration_button) - - def get_required_tip_length_calibration(self) -> WebElement: - """Tip length calibration.""" - return self.base.clickable_wrapper(SetupCalibration.required_tip_length_calibration_text_locator) diff --git a/app-testing/automation/resources/__init__.py b/app-testing/automation/resources/__init__.py deleted file mode 100644 index 8a25470edc9..00000000000 --- a/app-testing/automation/resources/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Definitions of resources needed in tests.""" diff --git a/app-testing/automation/resources/ot_robot.py b/app-testing/automation/resources/ot_robot.py deleted file mode 100644 index 2a7e7587140..00000000000 --- a/app-testing/automation/resources/ot_robot.py +++ /dev/null @@ -1,124 +0,0 @@ -"""Model the the Opentrons Robot.""" - -from typing import List - -import requests -from requests.adapters import HTTPAdapter -from requests.structures import CaseInsensitiveDict -from rich.console import Console -from urllib3.util.retry import Retry - -from automation.resources.robot_data import Module, RobotDataType - - -class OtRobot: - """Opentrons Robot.""" - - RETRIES = 3 - BACK_OFF_FACTOR = 0.3 - TIME_BETWEEN_RETRIES = 500 - ERROR_CODES = (500, 502, 503, 504) - - def __init__( - self, - console: Console, - robot_data: RobotDataType, - ) -> None: - """Initialize the robot.""" - self.alive: bool = False - self.console: Console = console - session = requests.session() - retry = Retry( - total=OtRobot.RETRIES, - read=OtRobot.RETRIES, - connect=OtRobot.RETRIES, - backoff_factor=OtRobot.BACK_OFF_FACTOR, - status_forcelist=OtRobot.ERROR_CODES, - ) - adapter = HTTPAdapter(max_retries=retry) - session.mount("http://", adapter) - session.headers = CaseInsensitiveDict({"Opentrons-Version": "3"}) - self.session: requests.Session = session - self.data: RobotDataType = robot_data - self.base_url: str = f"http://{self.data.host}:{self.data.port}" - self.console.print( - f"base_url for robot {self.data.display_name} is {self.base_url}", - style="white on blue", - ) - self.get_modules() - - def is_alive(self) -> bool: - """Is a robot available by http - request the openapi.json.""" - try: - response: requests.Response = self.session.get(f"{self.base_url}/openapi.json", timeout=5) - if response.status_code == 200: - self.alive = True - return self.alive - except requests.exceptions.ConnectionError: - self.console.print(f"[bold cyan]{self.data.display_name } robot is not reachable.") - self.alive = False - return self.alive - - def get_modules(self) -> None: - """Retrieve the modules from /modules.""" - modules: List[Module] = [] - if self.is_alive(): - response: requests.Response = self.session.get(f"{self.base_url}/modules", timeout=5) - if response.status_code == 200: - self.console.print( - f"{self.data.display_name } retrieved modules successfully.", - style="white on blue", - ) - for module in response.json()["data"]: - modules.append(Module(module["moduleModel"], module["id"], module["serialNumber"])) - self.modules: List[Module] = modules - - def deck_calibrated(self) -> bool: - """Is the deck calibrated.""" - response: requests.Response = self.session.get(f"{self.base_url}/calibration/status", timeout=2) - self.console.print(response.json()) - if response.status_code == 200: - return bool(response.json()["deckCalibration"]["status"] == "OK") - return False - - def pipettes_calibrated(self) -> bool: - """Are both pipetted offset and tip length calibrated. - - For now we will tightly couple this to our standard emulation pipette setup. - """ - response: requests.Response = self.session.get(f"{self.base_url}/calibration/pipette_offset", timeout=2) - self.console.print(response.json()) - if response.status_code == 200: - data = response.json()["data"] - if data == []: - return False - try: - left = [pipette for pipette in data if pipette["id"] == self.data.left_pipette][0] - right = [pipette for pipette in data if pipette["id"] == self.data.right_pipette][0] - left_offset: bool = len(left["offset"]) == 3 - right_offset: bool = len(right["offset"]) == 3 - return left_offset and right_offset - except (KeyError, IndexError): - pass - return False - - def tip_length_calibrated(self) -> bool: - """Are both pipetted offset and tip length calibrated. - - For now we will tightly couple this to our standard emulation pipette setup. - """ - response: requests.Response = self.session.get(f"{self.base_url}/calibration/tip_length", timeout=2) - self.console.print(response.json()) - if response.status_code == 200: - data = response.json()["data"] - if data == []: - return False - try: - left = [pipette for pipette in data if pipette["pipette"] == self.data.left_pipette.split("&")[0]][0] - right = [pipette for pipette in data if pipette["pipette"] == self.data.right_pipette.split("&")[0]][0] - left_tip_length: bool = left["tipLength"] > 0 - right_tip_length: bool = right["tipLength"] > 0 - return left_tip_length and right_tip_length - except KeyError: - pass - return False diff --git a/app-testing/automation/resources/robot_data.py b/app-testing/automation/resources/robot_data.py deleted file mode 100644 index c84f9735a12..00000000000 --- a/app-testing/automation/resources/robot_data.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Robot data.""" - -from dataclasses import dataclass -from typing import Literal - -module_types = Literal[ - "magneticModuleV1", - "magneticModuleV2", - "temperatureModuleV1", - "temperatureModuleV2", - "thermocyclerModuleV1", - "heaterShakerModuleV1", -] - - -@dataclass -class Module: - """Module types.""" - - type: module_types - id: str - serial: str - - -@dataclass -class Dev: - """Info about the robot brought up with 'make -C robot-server dev'.""" - - name: str = "dev" - display_name: str = "opentrons-dev" - host: str = "localhost" - port: str = "31950" - left_pipette: str = "" - right_pipette: str = "" - left_pipette_model: str = "" - right_pipette_model: str = "" - - -@dataclass -class EmulatedAlpha: - """Info about the robot brought up with emulation app-testing/ci-tools/ot2_with_all_modules.yaml.""" - - name: str = "emulated_alpha" - display_name: str = "opentrons-dev" - host: str = "localhost" - port: str = "31950" - left_pipette: str = "p300multi&left" - right_pipette: str = "p20single&right" - left_pipette_model: str = "P300 8-Channel GEN2" - right_pipette_model: str = "P20 Single-Channel GEN2" - - -@dataclass -class Kansas: - """A robot in Kansas used for testing.""" - - name: str = "kansas" - display_name: str = "kansas" - host: str = "192.168.71.89" - port: str = "31950" - left_pipette: str = "p300multi&left" - right_pipette: str = "p20single&right" - left_pipette_model: str = "P300 8-Channel GEN2" - right_pipette_model: str = "P20 Single-Channel GEN2" - - -RobotDataType = Dev | Kansas | EmulatedAlpha -ROBOT_MAPPING: dict[str, RobotDataType] = { - "dev": Dev(), - "kansas": Kansas(), - "emulated_alpha": EmulatedAlpha(), # expecting more configs in the future. -} diff --git a/app-testing/citools/generate_analyses.py b/app-testing/citools/generate_analyses.py index 67ad42c44c7..f1335b102ae 100644 --- a/app-testing/citools/generate_analyses.py +++ b/app-testing/citools/generate_analyses.py @@ -54,6 +54,7 @@ class AnalyzedProtocol: host_analysis_file: Path container_analysis_file: Path tag: str + custom_labware_paths: List[str] analysis_execution_time: Optional[float] = None command_exit_code: Optional[int] = None command_output: Optional[str] = None @@ -185,11 +186,18 @@ def container_analysis_path(protocol_file: Path, tag: str) -> Path: def generate_protocols(tag: str) -> List[AnalyzedProtocol]: + + # Since we do not have the specification for which labware to use + # we will use all labware in the host labware directory + all_custom_labware_paths = [str(host_path.relative_to(CONTAINER_LABWARE)) for host_path in list(Path(HOST_LABWARE).rglob("*.json"))] + def find_pd_protocols() -> List[AnalyzedProtocol]: # Check if the provided path is a valid directory if not HOST_PROTOCOLS_ROOT.is_dir(): raise NotADirectoryError(f"The path {HOST_PROTOCOLS_ROOT} is not a valid directory.") + nonlocal all_custom_labware_paths + # Recursively find all .json files json_files = list(HOST_PROTOCOLS_ROOT.rglob("*.json")) filtered_json_files = [file for file in json_files if has_designer_application(file)] @@ -198,7 +206,14 @@ def find_pd_protocols() -> List[AnalyzedProtocol]: relative_path = path.relative_to(HOST_PROTOCOLS_ROOT) updated_path = Path(CONTAINER_PROTOCOLS_ROOT, relative_path) pd_protocols.append( - AnalyzedProtocol(path, updated_path, host_analysis_path(path, tag), container_analysis_path(path, tag), tag) + AnalyzedProtocol( + host_protocol_file=path, + container_protocol_file=updated_path, + host_analysis_file=host_analysis_path(path, tag), + container_analysis_file=container_analysis_path(path, tag), + tag=tag, + custom_labware_paths=all_custom_labware_paths, + ) ) return pd_protocols @@ -207,6 +222,8 @@ def find_python_protocols() -> List[AnalyzedProtocol]: if not HOST_PROTOCOLS_ROOT.is_dir(): raise NotADirectoryError(f"The path {HOST_PROTOCOLS_ROOT} is not a valid directory.") + nonlocal all_custom_labware_paths + # Recursively find all .py files python_files = list(HOST_PROTOCOLS_ROOT.rglob("*.py")) py_protocols: List[AnalyzedProtocol] = [] @@ -215,7 +232,14 @@ def find_python_protocols() -> List[AnalyzedProtocol]: relative_path = path.relative_to(HOST_PROTOCOLS_ROOT) container_path = Path(CONTAINER_PROTOCOLS_ROOT, relative_path) py_protocols.append( - AnalyzedProtocol(path, container_path, host_analysis_path(path, tag), container_analysis_path(path, tag), tag=tag) + AnalyzedProtocol( + host_protocol_file=path, + container_protocol_file=container_path, + host_analysis_file=host_analysis_path(path, tag), + container_analysis_file=container_analysis_path(path, tag), + tag=tag, + custom_labware_paths=all_custom_labware_paths, + ) ) return py_protocols @@ -234,15 +258,20 @@ def remove_all_files_in_directory(directory: Path) -> None: console.print(f"Failed to delete {file_path}. Reason: {e}") -def container_custom_labware_paths() -> List[str]: - if HOST_LABWARE.is_dir(): - return [os.path.join(CONTAINER_LABWARE, file) for file in os.listdir(HOST_LABWARE) if file.endswith(".json")] - return [] +def protocol_custom_labware_paths_in_container(protocol: Protocol) -> List[str]: + if not HOST_LABWARE.is_dir() or protocol.custom_labware is None: + return [] + + return [ + str(os.path.join(CONTAINER_LABWARE, f"{file}.json")) + for file in protocol.custom_labware + if f"{file}.json" in os.listdir(HOST_LABWARE) + ] def analyze(protocol: AnalyzedProtocol, container: docker.models.containers.Container) -> bool: # Run the analyze command - command = f"python -I -m opentrons.cli analyze --json-output {protocol.container_analysis_file} {protocol.container_protocol_file} {' '.join(map(str, container_custom_labware_paths()))}" # noqa: E501 + command = f"python -I -m opentrons.cli analyze --json-output {protocol.container_analysis_file} {protocol.container_protocol_file} {' '.join(protocol.custom_labware_paths)}" # noqa: E501 start_time = time.time() timeout_duration = 30 # seconds try: @@ -311,7 +340,14 @@ def generate_analyses_from_test(tag: str, protocols: List[Protocol]) -> None: host_analysis_file = host_analysis_path(host_protocol_file, tag) container_analysis_file = container_analysis_path(host_protocol_file, tag) protocols_to_process.append( - AnalyzedProtocol(host_protocol_file, container_protocol_file, host_analysis_file, container_analysis_file, tag) + AnalyzedProtocol( + host_protocol_file, + container_protocol_file, + host_analysis_file, + container_analysis_file, + tag, + protocol_custom_labware_paths_in_container(test_protocol), + ) ) console.print(f"Analyzing {len(protocols_to_process)} protocol(s) against {tag}...") container = stop_and_restart_container(image_name) diff --git a/app-testing/citools/linux_get_chromedriver.sh b/app-testing/citools/linux_get_chromedriver.sh deleted file mode 100755 index c42e7d4b79b..00000000000 --- a/app-testing/citools/linux_get_chromedriver.sh +++ /dev/null @@ -1,18 +0,0 @@ -#! /bin/bash - -set -eo pipefail - -VERSION=$1 -URL="https://github.com/electron/electron/releases/download/v${VERSION}/chromedriver-v${VERSION}-linux-x64.zip" - -CHROMEAPP=google-chrome -if ! type -a google-chrome >/dev/null 2>&1; then - sudo apt-get update - sudo apt-get install -y google-chrome -fi -echo "Downloading chromedriver" -echo "URL: $URL" -wget -c -nc --retry-connrefused --tries=0 $URL -unzip -o -q chromedriver-v${VERSION}-linux-x64.zip -sudo mv chromedriver /usr/local/bin/chromedriver -rm chromedriver-v${VERSION}-linux-x64.zip diff --git a/app-testing/citools/mac_get_chromedriver.sh b/app-testing/citools/mac_get_chromedriver.sh deleted file mode 100755 index 4f498a1d99b..00000000000 --- a/app-testing/citools/mac_get_chromedriver.sh +++ /dev/null @@ -1,18 +0,0 @@ -set -eo pipefail - -VERSION=$1 -PLATFORM=$(uname -m) -if [[ "$PLATFORM" == "x86_64" ]] -then - PLATFORM=x64 -fi -CHROMEAPP="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" -wget -c -nc --retry-connrefused --tries=0 https://github.com/electron/electron/releases/download/v${VERSION}/chromedriver-v${VERSION}-darwin-${PLATFORM}.zip -unzip -o -q chromedriver-v${VERSION}-darwin-${PLATFORM}.zip -sudo mv chromedriver /usr/local/bin/chromedriver - -rm chromedriver-v${VERSION}-darwin-${PLATFORM}.zip -wget -c -nc --retry-connrefused --tries=0 https://github.com/electron/electron/releases/download/v${VERSION}/ffmpeg-v${VERSION}-darwin-${PLATFORM}.zip -unzip -o -q ffmpeg-v${VERSION}-darwin-${PLATFORM}.zip -sudo mv libffmpeg.dylib /usr/local/bin/libffmpeg.dylib -rm ffmpeg-v${VERSION}-darwin-${PLATFORM}.zip diff --git a/app-testing/citools/windows_get_chromedriver.ps1 b/app-testing/citools/windows_get_chromedriver.ps1 deleted file mode 100755 index b2e82deaec0..00000000000 --- a/app-testing/citools/windows_get_chromedriver.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -Param( - [string]$version -) - -Invoke-WebRequest "https://github.com/electron/electron/releases/download/v$version/chromedriver-v$version-win32-x64.zip" -OutFile chromedriver.zip -# https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md -Expand-Archive -Path chromedriver.zip -DestinationPath C:\SeleniumWebDrivers\ChromeDriver -Force -Remove-Item chromedriver.zip -[Environment]::SetEnvironmentVariable("Path", $Env:Path + ";C:\SeleniumWebDrivers\ChromeDriver", [EnvironmentVariableTarget]::Machine) diff --git a/app-testing/conftest.py b/app-testing/conftest.py deleted file mode 100644 index dc12b965acc..00000000000 --- a/app-testing/conftest.py +++ /dev/null @@ -1,117 +0,0 @@ -"""Pytest setup.""" - -import os -from typing import Generator, List, Optional - -import pytest -from automation.menus.left_menu import LeftMenu -from automation.pages.app_settings import AppSettings -from automation.resources.robot_data import ROBOT_MAPPING, RobotDataType -from dotenv import find_dotenv, load_dotenv -from rich import pretty, traceback -from rich.console import Console -from selenium import webdriver -from selenium.webdriver.chrome.options import Options -from selenium.webdriver.chrome.webdriver import WebDriver - -collect_ignore_glob = ["files/**/*.py"] - -_console = Console(color_system="auto") -pretty.install(console=_console) -traceback.install(console=_console) - - -# My setting overrides to false we give preference to System Environment Variables -# This is important for CI -if find_dotenv(): - load_dotenv(find_dotenv(), override=False) -elif find_dotenv(filename="example.env"): # example.env has our defaults - load_dotenv(find_dotenv(filename="example.env"), override=False) -else: - raise AssertionError("No .env or example.env file found.") - - -def _chrome_options() -> Options: - """Chrome options for setup.""" - options = Options() - executable_path = os.getenv("EXECUTABLE_PATH") - assert executable_path is not None, "EXECUTABLE_PATH environment variable must be set" - _console.print(f"EXECUTABLE_PATH is {executable_path}", style="white on blue") - options.binary_location = executable_path - options.add_argument("whitelisted-ips=''") - options.add_argument("disable-xss-auditor") - options.add_argument("disable-web-security") - options.add_argument("allow-running-insecure-content") - options.add_argument("no-sandbox") - options.add_argument("disable-setuid-sandbox") - options.add_argument("disable-popup-blocking") - options.add_argument("allow-elevated-browser") - return options - - -def add_localhost(driver: WebDriver, request: pytest.FixtureRequest) -> None: - """Add localhost using the app UI.""" - # This was necessary because - # os.environ["OT_APP_DISCOVERY__CANDIDATES"] = "localhost" was broken - # now preserving in case we want to use in the future - # how to call this method - # use .env to set LOCALHOST - # localhost: Optional[str] = os.getenv("LOCALHOST") - # if localhost: - # if localhost.lower() == "true": - # add_localhost(driver=driver, request=request) - app_settings: AppSettings = AppSettings(driver, _console, request.node.nodeid) - left_menu: LeftMenu = LeftMenu(driver, _console, request.node.nodeid) - left_menu.navigate("app-settings") - assert app_settings.get_app_settings_header().text == "App Settings" - assert app_settings.get_connect_robot_via_IP_header().is_displayed() - assert app_settings.get_connect_to_robot_via_IP_address_button().is_displayed() - app_settings.click_connect_to_robot_via_IP_address_button() - assert app_settings.get_textbox_to_enter_the_ip().is_displayed() - app_settings.click_add_ip_or_hostname() - app_settings.enter_hostname("localhost") - assert app_settings.get_add_button().is_displayed() - app_settings.click_add_button() - assert app_settings.get_done_button().is_displayed() - app_settings.click_done_button() - - -@pytest.fixture(scope="session") -def driver(request: pytest.FixtureRequest) -> Generator[WebDriver, None, None]: - """Pass standard Chrome options to a test.""" - update_channel = os.getenv("UPDATE_CHANNEL") - assert update_channel is not None, "UPDATE_CHANNEL environment variable must be set" - options = _chrome_options() - os.environ["OT_APP_ANALYTICS__SEEN_OPT_IN"] = "true" - os.environ["OT_APP_ANALYTICS__OPTED_IN"] = "true" - os.environ["OT_APP_ANALYTICS__APP_ID"] = "6dcc8733-c3e6-4ac4-b14f-638ede114ac5" - os.environ["OT_APP_ANALYTICS__USER_ID"] = "b806c211-3b21-4c5e-8b06-aedc58887cce" - os.environ["OT_APP_UPDATE__CHANNEL"] = update_channel - os.environ["OT_APP_LOG__LEVEL__CONSOLE"] = "error" - os.environ["OT_APP_DISCOVERY__CANDIDATES"] = "localhost" # fixed in 6.2 - with webdriver.Chrome(options=options) as driver: - _console.print("Driver Capabilities.", style="bright_yellow on blue") - _console.print(driver.capabilities) - localhost: Optional[str] = os.getenv("LOCALHOST") - if localhost: - if localhost.lower() == "true": - add_localhost(driver=driver, request=request) - yield driver - - -@pytest.fixture(scope="session") -def console() -> Console: - """Rich console for output.""" - return _console - - -@pytest.fixture(scope="session") -def robots() -> List[RobotDataType]: - """Robot data.""" - # provide all robot data to the tests - robots = ["dev", "kansas", "emulated_alpha"] - result = [] - for robot in robots: - robot_type = ROBOT_MAPPING[robot] - result.append(robot_type) - return result diff --git a/app-testing/example.env b/app-testing/example.env index 3061ea0e5a3..d9e273f680d 100644 --- a/app-testing/example.env +++ b/app-testing/example.env @@ -11,24 +11,35 @@ SLOWMO=TrUe HIGHLIGHT_SECONDS=.3 # default is 2 UPDATE_CHANNEL="alpha" # latest beta alpha LOCALHOST=false -# Analyses Snapshot test target -TARGET=edge + # run all tests # possible values in \automation\data\protocol_files.py # dynamically generate with make print-protocols APP_ANALYSIS_TEST_PROTOCOLS=" +Flex_X_v8_P1000_96_HS_GRIP_TC_TM_GripperCollisionWithTips, +OT2_S_v3_P300SGen1_None_Gen1PipetteSimple, +OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40, +OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps, +OT2_S_v6_P20S_P300M_TransferReTransferLiquid, +OT2_S_v6_P300M_P20S_HS_Smoke620release, +OT2_S_v6_P300M_P20S_MixTransferManyLiquids, +OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer, +OT2_S_v6_P1000S_None_SimpleTransfer, +OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests, +OT2_X_v6_P20S_None_SimpleTransfer, +OT2_X_v6_P20S_P300M_HS_HSCollision, +OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods, +Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria, +Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8, +Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4, +Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7, Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots, -Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment, -Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4, -Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x, -Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight, Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3, Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3, Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction, -Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction, -Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction, -Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2, +Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria, +Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight, Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots, Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules, Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures, @@ -47,6 +58,7 @@ Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero, Flex_S_v2_18_KAPA_Library_Quant, Flex_S_v2_18_NO_PIPETTES_GoldenRTP, Flex_S_v2_18_P1000_96_GRIP_HS_MB_TC_TM_Smoke, +Flex_S_v2_18_P1000_96_GRIP_HS_MB_TC_TM_Smoke, Flex_S_v2_18_P1000_96_TipTrackingBug, Flex_S_v2_18_QIASeq_FX_48x, Flex_S_v2_18_kapahyperplus, @@ -69,6 +81,7 @@ Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4, Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2, Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3, Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4, +Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol, Flex_X_v2_16_P1000_96_DropTipsWithNoTrash, Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin, Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict, @@ -76,10 +89,12 @@ Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip, Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid, Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips, Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict, -Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol, Flex_X_v2_18_NO_PIPETTES_DescriptionTooLongRTP, Flex_X_v2_18_NO_PIPETTES_ReservedWord, -Flex_X_v8_P1000_96_HS_GRIP_TC_TM_GripperCollisionWithTips, +OT2_S_v2_2_P300S_None_MM1_MM2_EngageMagHeightFromBase, +OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix, +OT2_S_v2_4_P300M_None_MM_TM_Zymo, +OT2_S_v2_7_P20S_None_Walkthrough, OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift, OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError, OT2_S_v2_12_P300M_P20S_FailOnRun, @@ -88,18 +103,18 @@ OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release, OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots, OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3, OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots, -OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3, OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes, +OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3, OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots, OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock, -OT2_S_v2_16_P300M_P20S_HS_TC_TM_SmokeTestV3, +OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume, OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume, OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes, -OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume, +OT2_S_v2_16_P300M_P20S_HS_TC_TM_SmokeTestV3, OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting, OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots, -OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3, OT2_S_v2_17_P300M_P20S_HS_TC_TM_dispense_changes, +OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3, OT2_S_v2_18_NO_PIPETTES_GoldenRTP_OT2, OT2_S_v2_18_None_None_duplicateChoiceValue, OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3, @@ -119,14 +134,57 @@ OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError, OT2_X_v2_13_None_None_PythonSyntaxError, OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1, OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin2, +OT2_X_v2_18_None_None_duplicateRTPVariableName, OT2_X_v2_18_None_None_NoRTPdisplay_name, OT2_X_v2_18_None_None_StrRTPwith_unit, -OT2_X_v2_18_None_None_duplicateRTPVariableName, -OT2_X_v2_7_P300S_TwinningError, -OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests, -OT2_X_v6_P20S_None_SimpleTransfer, -OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods +pl_96_ch_demo_rtp_with_hs, +pl_96_ch_demo_rtp, +pl_AMPure_XP_48x_v8, +pl_BacteriaInoculation_Flex_6plates, +pl_BCApeptideassay, +pl_Bradford_proteinassay, +pl_cherrypicking_csv_airgap, +pl_Dynabeads_IP_Flex_96well_final, +pl_Dynabeads_IP_Flex_96well_RIT_final, +pl_EM_seq_48Samples_AllSteps_Edits_150, +pl_ExpressPlex_96_final, +pl_ExpressPlex_Pooling_Final, +pl_Flex_customizable_serial_dilution_upload, +pl_Flex_Protein_Digestion_Protocol, +pl_Hyperplus_tiptracking_V4_final, +pl_Illumina_DNA_PCR_Free, +pl_Illumina_DNA_Prep_48x_v8, +pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol, +pl_KAPA_Library_Quant_48_v8, +pl_langone_pt2_ribo, +pl_langone_ribo_pt1_ramp, +pl_M_N_Nucleomag_DNA_Flex_multi, +pl_MagMax_RNA_Cells_Flex_96_Channel, +pl_MagMax_RNA_Cells_Flex_multi, +pl_microBioID_beads_touchtip, +pl_Nanopore_Genomic_Ligation_v5_Final, +pl_NiNTA_Flex_96well_final, +pl_NiNTA_Flex_96well_PlatePrep_final, +pl_Normalization_with_PCR, +pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel, +pl_Omega_HDQ_DNA_Bacteria_Flex_multi, +pl_Omega_HDQ_DNA_Cells_Flex_96_channel, +pl_Omega_HDQ_DNA_Cells_Flex_multi, +pl_QIAseq_FX_24x_Normalizer_Workflow_B, +pl_QIASeq_FX_48x_v8, +pl_sample_dilution_with_96_channel_pipette, +pl_SamplePrep_MS_Cleanup_Flex_upto96, +pl_SamplePrep_MS_Digest_Flex_upto96, +pl_sigdx_part2, +pl_Takara_InFusionSnapAssembly_Flex, +pl_cherrypicking_flex_v3, +pl_normalization_with_csv, +pl_Zymo_Magbead_DNA_Cells_Flex_96_channel, +pl_Zymo_Magbead_DNA_Cells_Flex_multi, +pl_Zymo_Quick_RNA_Cells_Flex_96_Channel, +pl_Zymo_Quick_RNA_Cells_Flex_multi " + APP_ANALYSIS_TEST_PROTOCOLS_WITH_OVERRIDES=" Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP, Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice, diff --git a/app-testing/files/labware/Axygen_96_Well_Plate_200_uL.json b/app-testing/files/labware/Axygen_96_Well_Plate_200_uL.json new file mode 100644 index 00000000000..dafcbbea7e2 --- /dev/null +++ b/app-testing/files/labware/Axygen_96_Well_Plate_200_uL.json @@ -0,0 +1 @@ +{"brand":{"brand":"Axygen","brandId":["PMI110-07A"]},"wells":{"A1":{"x":14.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A2":{"x":23.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A3":{"x":32.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A4":{"x":41.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A5":{"x":50.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A6":{"x":59.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A7":{"x":68.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A8":{"x":77.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A9":{"x":86.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B1":{"x":14.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B2":{"x":23.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B3":{"x":32.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B4":{"x":41.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B5":{"x":50.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B6":{"x":59.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B7":{"x":68.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B8":{"x":77.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B9":{"x":86.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C1":{"x":14.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C2":{"x":23.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C3":{"x":32.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C4":{"x":41.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C5":{"x":50.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C6":{"x":59.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C7":{"x":68.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C8":{"x":77.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C9":{"x":86.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D1":{"x":14.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D2":{"x":23.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D3":{"x":32.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D4":{"x":41.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D5":{"x":50.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D6":{"x":59.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D7":{"x":68.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D8":{"x":77.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D9":{"x":86.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E1":{"x":14.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E2":{"x":23.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E3":{"x":32.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E4":{"x":41.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E5":{"x":50.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E6":{"x":59.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E7":{"x":68.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E8":{"x":77.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E9":{"x":86.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F1":{"x":14.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F2":{"x":23.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F3":{"x":32.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F4":{"x":41.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F5":{"x":50.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F6":{"x":59.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F7":{"x":68.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F8":{"x":77.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F9":{"x":86.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G1":{"x":14.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G2":{"x":23.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G3":{"x":32.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G4":{"x":41.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G5":{"x":50.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G6":{"x":59.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G7":{"x":68.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G8":{"x":77.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G9":{"x":86.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H1":{"x":14.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H2":{"x":23.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H3":{"x":32.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H4":{"x":41.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H5":{"x":50.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H6":{"x":59.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H7":{"x":68.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H8":{"x":77.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H9":{"x":86.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A10":{"x":95.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A11":{"x":104.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"A12":{"x":113.38,"y":74.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B10":{"x":95.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B11":{"x":104.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"B12":{"x":113.38,"y":65.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C10":{"x":95.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C11":{"x":104.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"C12":{"x":113.38,"y":56.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D10":{"x":95.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D11":{"x":104.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"D12":{"x":113.38,"y":47.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E10":{"x":95.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E11":{"x":104.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"E12":{"x":113.38,"y":38.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F10":{"x":95.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F11":{"x":104.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"F12":{"x":113.38,"y":29.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G10":{"x":95.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G11":{"x":104.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"G12":{"x":113.38,"y":20.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H10":{"x":95.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H11":{"x":104.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200},"H12":{"x":113.38,"y":11.24,"z":6.46,"depth":20.75,"shape":"circular","diameter":5.3,"totalLiquidVolume":200}},"groups":[{"wells":["A1","B1","C1","D1","E1","F1","G1","H1","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","A10","B10","C10","D10","E10","F10","G10","H10","A11","B11","C11","D11","E11","F11","G11","H11","A12","B12","C12","D12","E12","F12","G12","H12"],"metadata":{"wellBottomShape":"v"}}],"version":1,"metadata":{"tags":[],"displayName":"Axygen 96 Well Plate 200 µL","displayCategory":"wellPlate","displayVolumeUnits":"µL"},"ordering":[["A1","B1","C1","D1","E1","F1","G1","H1"],["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"],["A10","B10","C10","D10","E10","F10","G10","H10"],["A11","B11","C11","D11","E11","F11","G11","H11"],["A12","B12","C12","D12","E12","F12","G12","H12"]],"namespace":"custom_beta","dimensions":{"xDimension":127.76,"yDimension":85.48,"zDimension":27.21},"parameters":{"format":"irregular","quirks":[],"loadName":"axygen_96_wellplate_200ul","isTiprack":false,"isMagneticModuleCompatible":false},"schemaVersion":2,"cornerOffsetFromSlot":{"x":0,"y":0,"z":0}} \ No newline at end of file diff --git a/app-testing/files/labware/BioRad_96_Well_Plate_200_uL.json b/app-testing/files/labware/BioRad_96_Well_Plate_200_uL.json new file mode 100644 index 00000000000..4cbd4b87def --- /dev/null +++ b/app-testing/files/labware/BioRad_96_Well_Plate_200_uL.json @@ -0,0 +1 @@ +{"brand":{"brand":"BioRad","brandId":["hsp9601"]},"wells":{"A1":{"x":14.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A2":{"x":23.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A3":{"x":32.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A4":{"x":41.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A5":{"x":50.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A6":{"x":59.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A7":{"x":68.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A8":{"x":77.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A9":{"x":86.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B1":{"x":14.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B2":{"x":23.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B3":{"x":32.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B4":{"x":41.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B5":{"x":50.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B6":{"x":59.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B7":{"x":68.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B8":{"x":77.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B9":{"x":86.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C1":{"x":14.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C2":{"x":23.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C3":{"x":32.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C4":{"x":41.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C5":{"x":50.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C6":{"x":59.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C7":{"x":68.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C8":{"x":77.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C9":{"x":86.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D1":{"x":14.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D2":{"x":23.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D3":{"x":32.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D4":{"x":41.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D5":{"x":50.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D6":{"x":59.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D7":{"x":68.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D8":{"x":77.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D9":{"x":86.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E1":{"x":14.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E2":{"x":23.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E3":{"x":32.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E4":{"x":41.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E5":{"x":50.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E6":{"x":59.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E7":{"x":68.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E8":{"x":77.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E9":{"x":86.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F1":{"x":14.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F2":{"x":23.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F3":{"x":32.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F4":{"x":41.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F5":{"x":50.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F6":{"x":59.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F7":{"x":68.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F8":{"x":77.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F9":{"x":86.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G1":{"x":14.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G2":{"x":23.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G3":{"x":32.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G4":{"x":41.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G5":{"x":50.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G6":{"x":59.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G7":{"x":68.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G8":{"x":77.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G9":{"x":86.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H1":{"x":14.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H2":{"x":23.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H3":{"x":32.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H4":{"x":41.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H5":{"x":50.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H6":{"x":59.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H7":{"x":68.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H8":{"x":77.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H9":{"x":86.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A10":{"x":95.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A11":{"x":104.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"A12":{"x":113.38,"y":74.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B10":{"x":95.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B11":{"x":104.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"B12":{"x":113.38,"y":65.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C10":{"x":95.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C11":{"x":104.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"C12":{"x":113.38,"y":56.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D10":{"x":95.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D11":{"x":104.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"D12":{"x":113.38,"y":47.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E10":{"x":95.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E11":{"x":104.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"E12":{"x":113.38,"y":38.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F10":{"x":95.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F11":{"x":104.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"F12":{"x":113.38,"y":29.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G10":{"x":95.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G11":{"x":104.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"G12":{"x":113.38,"y":20.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H10":{"x":95.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H11":{"x":104.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200},"H12":{"x":113.38,"y":11.24,"z":1.25,"depth":14.81,"shape":"circular","diameter":5.46,"totalLiquidVolume":200}},"groups":[{"wells":["A1","B1","C1","D1","E1","F1","G1","H1","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","A10","B10","C10","D10","E10","F10","G10","H10","A11","B11","C11","D11","E11","F11","G11","H11","A12","B12","C12","D12","E12","F12","G12","H12"],"metadata":{"wellBottomShape":"v"}}],"version":1,"metadata":{"tags":[],"displayName":"BioRad 96 Well Plate 200 µL","displayCategory":"wellPlate","displayVolumeUnits":"µL"},"ordering":[["A1","B1","C1","D1","E1","F1","G1","H1"],["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"],["A10","B10","C10","D10","E10","F10","G10","H10"],["A11","B11","C11","D11","E11","F11","G11","H11"],["A12","B12","C12","D12","E12","F12","G12","H12"]],"namespace":"custom_beta","dimensions":{"xDimension":127.76,"yDimension":85.48,"zDimension":16.06},"parameters":{"format":"irregular","quirks":[],"loadName":"biorad_96_wellplate_200ul_pcr","isTiprack":false,"isMagneticModuleCompatible":false},"schemaVersion":2,"cornerOffsetFromSlot":{"x":0,"y":0,"z":0},"stackingOffsetWithLabware":{"opentrons_96_pcr_adapter":{"x":0,"y":0,"z":13.41}}} \ No newline at end of file diff --git a/app-testing/files/labware/Eppendorf_96_Well_Plate_150_uL.json b/app-testing/files/labware/Eppendorf_96_Well_Plate_150_uL.json new file mode 100644 index 00000000000..3fed0a3260d --- /dev/null +++ b/app-testing/files/labware/Eppendorf_96_Well_Plate_150_uL.json @@ -0,0 +1 @@ +{"brand":{"brand":"Eppendorf","brandId":[]},"wells":{"A1":{"x":14.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A2":{"x":23.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A3":{"x":32.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A4":{"x":41.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A5":{"x":50.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A6":{"x":59.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A7":{"x":68.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A8":{"x":77.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A9":{"x":86.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B1":{"x":14.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B2":{"x":23.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B3":{"x":32.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B4":{"x":41.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B5":{"x":50.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B6":{"x":59.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B7":{"x":68.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B8":{"x":77.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B9":{"x":86.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C1":{"x":14.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C2":{"x":23.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C3":{"x":32.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C4":{"x":41.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C5":{"x":50.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C6":{"x":59.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C7":{"x":68.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C8":{"x":77.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C9":{"x":86.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D1":{"x":14.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D2":{"x":23.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D3":{"x":32.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D4":{"x":41.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D5":{"x":50.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D6":{"x":59.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D7":{"x":68.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D8":{"x":77.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D9":{"x":86.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E1":{"x":14.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E2":{"x":23.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E3":{"x":32.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E4":{"x":41.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E5":{"x":50.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E6":{"x":59.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E7":{"x":68.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E8":{"x":77.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E9":{"x":86.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F1":{"x":14.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F2":{"x":23.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F3":{"x":32.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F4":{"x":41.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F5":{"x":50.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F6":{"x":59.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F7":{"x":68.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F8":{"x":77.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F9":{"x":86.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G1":{"x":14.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G2":{"x":23.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G3":{"x":32.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G4":{"x":41.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G5":{"x":50.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G6":{"x":59.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G7":{"x":68.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G8":{"x":77.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G9":{"x":86.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H1":{"x":14.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H2":{"x":23.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H3":{"x":32.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H4":{"x":41.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H5":{"x":50.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H6":{"x":59.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H7":{"x":68.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H8":{"x":77.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H9":{"x":86.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A10":{"x":95.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A11":{"x":104.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"A12":{"x":113.38,"y":74.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B10":{"x":95.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B11":{"x":104.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"B12":{"x":113.38,"y":65.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C10":{"x":95.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C11":{"x":104.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"C12":{"x":113.38,"y":56.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D10":{"x":95.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D11":{"x":104.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"D12":{"x":113.38,"y":47.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E10":{"x":95.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E11":{"x":104.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"E12":{"x":113.38,"y":38.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F10":{"x":95.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F11":{"x":104.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"F12":{"x":113.38,"y":29.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G10":{"x":95.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G11":{"x":104.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"G12":{"x":113.38,"y":20.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H10":{"x":95.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H11":{"x":104.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150},"H12":{"x":113.38,"y":11.24,"z":0.7,"depth":14.6,"shape":"circular","diameter":5.5,"totalLiquidVolume":150}},"groups":[{"wells":["A1","B1","C1","D1","E1","F1","G1","H1","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","A10","B10","C10","D10","E10","F10","G10","H10","A11","B11","C11","D11","E11","F11","G11","H11","A12","B12","C12","D12","E12","F12","G12","H12"],"metadata":{"wellBottomShape":"v"}}],"version":1,"metadata":{"tags":[],"displayName":"Eppendorf 96 Well Plate 150 µL","displayCategory":"wellPlate","displayVolumeUnits":"µL"},"ordering":[["A1","B1","C1","D1","E1","F1","G1","H1"],["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"],["A10","B10","C10","D10","E10","F10","G10","H10"],["A11","B11","C11","D11","E11","F11","G11","H11"],["A12","B12","C12","D12","E12","F12","G12","H12"]],"namespace":"custom_beta","dimensions":{"xDimension":127.76,"yDimension":85.48,"zDimension":15.3},"parameters":{"format":"irregular","quirks":[],"loadName":"eppendorf_96_wellplate_150ul","isTiprack":false,"isMagneticModuleCompatible":false},"schemaVersion":2,"cornerOffsetFromSlot":{"x":0,"y":0,"z":0}} \ No newline at end of file diff --git a/app-testing/files/labware/Macherey_Nagel_deepwell_plate_2200ul.json b/app-testing/files/labware/Macherey_Nagel_deepwell_plate_2200ul.json new file mode 100644 index 00000000000..4824a30e9ae --- /dev/null +++ b/app-testing/files/labware/Macherey_Nagel_deepwell_plate_2200ul.json @@ -0,0 +1 @@ +{"brand":{"brand":"Macherey-Nagel","brandId":[]},"wells":{"A1":{"x":14.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A2":{"x":23.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A3":{"x":32.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A4":{"x":41.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A5":{"x":50.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A6":{"x":59.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A7":{"x":68.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A8":{"x":77.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A9":{"x":86.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B1":{"x":14.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B2":{"x":23.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B3":{"x":32.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B4":{"x":41.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B5":{"x":50.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B6":{"x":59.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B7":{"x":68.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B8":{"x":77.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B9":{"x":86.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C1":{"x":14.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C2":{"x":23.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C3":{"x":32.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C4":{"x":41.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C5":{"x":50.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C6":{"x":59.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C7":{"x":68.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C8":{"x":77.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C9":{"x":86.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D1":{"x":14.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D2":{"x":23.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D3":{"x":32.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D4":{"x":41.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D5":{"x":50.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D6":{"x":59.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D7":{"x":68.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D8":{"x":77.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D9":{"x":86.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E1":{"x":14.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E2":{"x":23.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E3":{"x":32.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E4":{"x":41.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E5":{"x":50.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E6":{"x":59.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E7":{"x":68.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E8":{"x":77.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E9":{"x":86.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F1":{"x":14.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F2":{"x":23.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F3":{"x":32.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F4":{"x":41.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F5":{"x":50.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F6":{"x":59.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F7":{"x":68.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F8":{"x":77.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F9":{"x":86.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G1":{"x":14.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G2":{"x":23.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G3":{"x":32.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G4":{"x":41.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G5":{"x":50.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G6":{"x":59.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G7":{"x":68.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G8":{"x":77.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G9":{"x":86.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H1":{"x":14.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H2":{"x":23.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H3":{"x":32.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H4":{"x":41.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H5":{"x":50.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H6":{"x":59.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H7":{"x":68.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H8":{"x":77.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H9":{"x":86.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A10":{"x":95.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A11":{"x":104.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"A12":{"x":113.65,"y":73.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B10":{"x":95.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B11":{"x":104.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"B12":{"x":113.65,"y":64.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C10":{"x":95.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C11":{"x":104.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"C12":{"x":113.65,"y":55.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D10":{"x":95.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D11":{"x":104.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"D12":{"x":113.65,"y":46.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E10":{"x":95.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E11":{"x":104.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"E12":{"x":113.65,"y":37.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F10":{"x":95.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F11":{"x":104.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"F12":{"x":113.65,"y":28.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G10":{"x":95.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G11":{"x":104.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"G12":{"x":113.65,"y":19.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H10":{"x":95.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H11":{"x":104.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200},"H12":{"x":113.65,"y":10.25,"z":1.5,"depth":42.5,"shape":"rectangular","xDimension":8,"yDimension":8,"totalLiquidVolume":2200}},"groups":[{"wells":["A1","B1","C1","D1","E1","F1","G1","H1","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","A10","B10","C10","D10","E10","F10","G10","H10","A11","B11","C11","D11","E11","F11","G11","H11","A12","B12","C12","D12","E12","F12","G12","H12"],"metadata":{"wellBottomShape":"u"}}],"version":1,"metadata":{"tags":[],"displayName":"Macherey-Nagel_deepwell_plate_2200ul","displayCategory":"wellPlate","displayVolumeUnits":"µL"},"ordering":[["A1","B1","C1","D1","E1","F1","G1","H1"],["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"],["A10","B10","C10","D10","E10","F10","G10","H10"],["A11","B11","C11","D11","E11","F11","G11","H11"],["A12","B12","C12","D12","E12","F12","G12","H12"]],"gripForce":15,"namespace":"custom_beta","dimensions":{"xDimension":127,"yDimension":85,"zDimension":44},"parameters":{"format":"irregular","quirks":[],"loadName":"macherey_nagel_dwplate_2200ul","isTiprack":false,"isMagneticModuleCompatible":false},"schemaVersion":2,"cornerOffsetFromSlot":{"x":0,"y":0,"z":0},"stackingOffsetWithModule":{"magneticBlockV1":{"x":0,"y":0,"z":0.3}},"stackingOffsetWithLabware":{"opentrons_universal_flat_adapter":{"x":-1.5,"y":0,"z":7.1}},"gripHeightFromLabwareBottom":21.9} \ No newline at end of file diff --git a/app-testing/files/labware/Omni_Plate_with_Tilt_Adapter.json b/app-testing/files/labware/Omni_Plate_with_Tilt_Adapter.json new file mode 100644 index 00000000000..6871a6f533f --- /dev/null +++ b/app-testing/files/labware/Omni_Plate_with_Tilt_Adapter.json @@ -0,0 +1 @@ +{"brand":{"brand":"generic"},"wells":{"A1":{"x":14.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A2":{"x":23.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A3":{"x":32.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A4":{"x":41.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A5":{"x":50.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A6":{"x":59.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A7":{"x":68.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A8":{"x":77.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A9":{"x":86.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B1":{"x":14.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B2":{"x":23.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B3":{"x":32.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B4":{"x":41.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B5":{"x":50.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B6":{"x":59.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B7":{"x":68.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B8":{"x":77.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B9":{"x":86.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C1":{"x":14.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C2":{"x":23.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C3":{"x":32.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C4":{"x":41.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C5":{"x":50.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C6":{"x":59.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C7":{"x":68.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C8":{"x":77.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C9":{"x":86.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D1":{"x":14.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D2":{"x":23.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D3":{"x":32.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D4":{"x":41.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D5":{"x":50.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D6":{"x":59.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D7":{"x":68.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D8":{"x":77.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D9":{"x":86.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E1":{"x":14.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E2":{"x":23.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E3":{"x":32.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E4":{"x":41.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E5":{"x":50.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E6":{"x":59.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E7":{"x":68.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E8":{"x":77.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E9":{"x":86.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F1":{"x":14.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F2":{"x":23.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F3":{"x":32.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F4":{"x":41.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F5":{"x":50.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F6":{"x":59.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F7":{"x":68.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F8":{"x":77.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F9":{"x":86.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G1":{"x":14.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G2":{"x":23.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G3":{"x":32.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G4":{"x":41.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G5":{"x":50.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G6":{"x":59.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G7":{"x":68.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G8":{"x":77.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G9":{"x":86.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H1":{"x":14.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H2":{"x":23.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H3":{"x":32.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H4":{"x":41.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H5":{"x":50.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H6":{"x":59.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H7":{"x":68.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H8":{"x":77.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H9":{"x":86.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A10":{"x":95.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A11":{"x":104.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"A12":{"x":113.4,"y":74.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B10":{"x":95.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B11":{"x":104.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"B12":{"x":113.4,"y":65.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C10":{"x":95.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C11":{"x":104.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"C12":{"x":113.4,"y":56.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D10":{"x":95.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D11":{"x":104.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"D12":{"x":113.4,"y":47.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E10":{"x":95.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E11":{"x":104.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"E12":{"x":113.4,"y":38.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F10":{"x":95.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F11":{"x":104.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"F12":{"x":113.4,"y":29.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G10":{"x":95.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G11":{"x":104.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"G12":{"x":113.4,"y":20.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H10":{"x":95.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H11":{"x":104.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360},"H12":{"x":113.4,"y":11.27,"z":60.4,"depth":11.7,"shape":"circular","diameter":6.86,"totalLiquidVolume":360}},"groups":[{"wells":["A1","B1","C1","D1","E1","F1","G1","H1","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","A10","B10","C10","D10","E10","F10","G10","H10","A11","B11","C11","D11","E11","F11","G11","H11","A12","B12","C12","D12","E12","F12","G12","H12"],"metadata":{"wellBottomShape":"flat"}}],"version":1,"metadata":{"tags":[],"displayName":"Omni Plate with Tilt Adapter","displayCategory":"wellPlate","displayVolumeUnits":"µL"},"ordering":[["A1","B1","C1","D1","E1","F1","G1","H1"],["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"],["A10","B10","C10","D10","E10","F10","G10","H10"],["A11","B11","C11","D11","E11","F11","G11","H11"],["A12","B12","C12","D12","E12","F12","G12","H12"]],"namespace":"custom_beta","dimensions":{"xDimension":127.76,"yDimension":85.47,"zDimension":72.1},"parameters":{"format":"96Standard","loadName":"omni_plate_tilt_adapter","isTiprack":false,"isMagneticModuleCompatible":false},"schemaVersion":2,"cornerOffsetFromSlot":{"x":0,"y":0,"z":0}} \ No newline at end of file diff --git a/app-testing/files/labware/Omni_Tray_Single_Well_Plate.json b/app-testing/files/labware/Omni_Tray_Single_Well_Plate.json new file mode 100644 index 00000000000..50f67367b4a --- /dev/null +++ b/app-testing/files/labware/Omni_Tray_Single_Well_Plate.json @@ -0,0 +1 @@ +{"brand":{"brand":"Thermo Fisher Scientific","brandId":["165218"]},"wells":{"A1":{"x":63.75,"y":42.82,"z":2.8,"depth":11.7,"shape":"rectangular","xDimension":123.7,"yDimension":81.3,"totalLiquidVolume":90000}},"groups":[{"wells":["A1"],"metadata":{"wellBottomShape":"flat"}}],"version":1,"metadata":{"tags":[],"displayName":"OmniTray Single-Well Plate","displayCategory":"wellPlate","displayVolumeUnits":"µL"},"ordering":[["A1"]],"namespace":"custom_beta","dimensions":{"xDimension":127.76,"yDimension":85.47,"zDimension":14.5},"parameters":{"format":"irregular","quirks":["centerMultichannelOnWells","touchTipDisabled"],"loadName":"omintray_1well_plate","isTiprack":false,"isMagneticModuleCompatible":false},"schemaVersion":2,"cornerOffsetFromSlot":{"x":0,"y":0,"z":0}} \ No newline at end of file diff --git a/app-testing/files/labware/Thermo_96_Well_Plate_2200_uL.json b/app-testing/files/labware/Thermo_96_Well_Plate_2200_uL.json new file mode 100644 index 00000000000..a9f18825805 --- /dev/null +++ b/app-testing/files/labware/Thermo_96_Well_Plate_2200_uL.json @@ -0,0 +1 @@ +{"brand":{"brand":"Thermo","brandId":["Abgene AB-0661"]},"wells":{"A1":{"x":14.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A2":{"x":23.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A3":{"x":32.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A4":{"x":41.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A5":{"x":50.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A6":{"x":59.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A7":{"x":68.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A8":{"x":77.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A9":{"x":86.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B1":{"x":14.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B2":{"x":23.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B3":{"x":32.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B4":{"x":41.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B5":{"x":50.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B6":{"x":59.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B7":{"x":68.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B8":{"x":77.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B9":{"x":86.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C1":{"x":14.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C2":{"x":23.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C3":{"x":32.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C4":{"x":41.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C5":{"x":50.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C6":{"x":59.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C7":{"x":68.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C8":{"x":77.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C9":{"x":86.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D1":{"x":14.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D2":{"x":23.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D3":{"x":32.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D4":{"x":41.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D5":{"x":50.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D6":{"x":59.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D7":{"x":68.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D8":{"x":77.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D9":{"x":86.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E1":{"x":14.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E2":{"x":23.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E3":{"x":32.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E4":{"x":41.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E5":{"x":50.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E6":{"x":59.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E7":{"x":68.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E8":{"x":77.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E9":{"x":86.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F1":{"x":14.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F2":{"x":23.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F3":{"x":32.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F4":{"x":41.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F5":{"x":50.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F6":{"x":59.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F7":{"x":68.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F8":{"x":77.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F9":{"x":86.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G1":{"x":14.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G2":{"x":23.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G3":{"x":32.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G4":{"x":41.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G5":{"x":50.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G6":{"x":59.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G7":{"x":68.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G8":{"x":77.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G9":{"x":86.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H1":{"x":14.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H2":{"x":23.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H3":{"x":32.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H4":{"x":41.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H5":{"x":50.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H6":{"x":59.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H7":{"x":68.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H8":{"x":77.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H9":{"x":86.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A10":{"x":95.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A11":{"x":104.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"A12":{"x":113.38,"y":74.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B10":{"x":95.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B11":{"x":104.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"B12":{"x":113.38,"y":65.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C10":{"x":95.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C11":{"x":104.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"C12":{"x":113.38,"y":56.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D10":{"x":95.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D11":{"x":104.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"D12":{"x":113.38,"y":47.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E10":{"x":95.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E11":{"x":104.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"E12":{"x":113.38,"y":38.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F10":{"x":95.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F11":{"x":104.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"F12":{"x":113.38,"y":29.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G10":{"x":95.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G11":{"x":104.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"G12":{"x":113.38,"y":20.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H10":{"x":95.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H11":{"x":104.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200},"H12":{"x":113.38,"y":11.24,"z":3.14,"depth":39.36,"shape":"rectangular","xDimension":8.4,"yDimension":8.4,"totalLiquidVolume":2200}},"groups":[{"wells":["A1","B1","C1","D1","E1","F1","G1","H1","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","A10","B10","C10","D10","E10","F10","G10","H10","A11","B11","C11","D11","E11","F11","G11","H11","A12","B12","C12","D12","E12","F12","G12","H12"],"metadata":{"wellBottomShape":"u"}}],"version":1,"metadata":{"tags":[],"displayName":"Thermo 96 Well Plate 2200 µL","displayCategory":"wellPlate","displayVolumeUnits":"µL"},"ordering":[["A1","B1","C1","D1","E1","F1","G1","H1"],["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"],["A10","B10","C10","D10","E10","F10","G10","H10"],["A11","B11","C11","D11","E11","F11","G11","H11"],["A12","B12","C12","D12","E12","F12","G12","H12"]],"namespace":"custom_beta","dimensions":{"xDimension":127.76,"yDimension":85.48,"zDimension":42.5},"parameters":{"format":"irregular","quirks":[],"loadName":"thermo_96_wellplate_2200ul","isTiprack":false,"isMagneticModuleCompatible":false},"schemaVersion":2,"cornerOffsetFromSlot":{"x":0,"y":0,"z":0}} \ No newline at end of file diff --git a/app-testing/files/protocols/Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria.py b/app-testing/files/protocols/Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria.py new file mode 100644 index 00000000000..2c1877d2595 --- /dev/null +++ b/app-testing/files/protocols/Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria.py @@ -0,0 +1,346 @@ +from opentrons.types import Point +import json +import os +import math +from time import sleep +from opentrons import types +import numpy as np + +metadata = { + "protocolName": "Omega HDQ DNA Extraction: Bacteria 96 channel testing", + "author": "Zach Galluzzo ", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.15", +} + +dry_run = False +HS_SLOT = 1 +USE_GRIPPER = True + +ABR_TEST = True +if ABR_TEST == True: + DRYRUN = True # True = skip incubation times, shorten mix, for testing purposes + TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack +else: + DRYRUN = False # True = skip incubation times, shorten mix, for testing purposes + TIP_TRASH = True + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + + # *****If drying beads does not produce same results- can eliminate waste in slot 12 and add extra elution reservoir*** + + # Same for all HDQ Extractions + deepwell_type = "nest_96_wellplate_2ml_deep" + wash_vol = 600 + if not dry_run: + settling_time = 2 + num_washes = 3 + if dry_run: + settling_time = 0.5 + num_washes = 1 + + h_s = ctx.load_module("heaterShakerModuleV1", HS_SLOT) + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + TL_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate 1") + TL_samples = TL_plate.wells()[0] + sample_plate = ctx.load_labware(deepwell_type, "C3", "Sample Plate 2") + samples_m = sample_plate.wells()[0] + + temp = ctx.load_module("temperature module gen2", "D3") + temp_block = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = temp_block.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate/ Reservoir") + elution_res = elutionplate.wells()[0] + magblock = ctx.load_module("magneticBlockV1", "C1") + # elution_two = ctx.load_labware(deepwell_type, '12').wells()[0] + + TL_res = ctx.load_labware(deepwell_type, "D2", "TL reservoir").wells()[0] + AL_res = ctx.load_labware(deepwell_type, "C2", "AL reservoir").wells()[0] + wash1_res = ctx.load_labware(deepwell_type, "B1", "Wash 1 reservoir").wells()[0] + wash2_res = ctx.load_labware(deepwell_type, "B2", "Wash 2 reservoir").wells()[0] + bind_res = ctx.load_labware(deepwell_type, "B3", "Beads and Binding reservoir").wells()[0] + + # Load tips + tipsbox = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", adapter="opentrons_flex_96_tiprack_adapter") + tips = tipsbox.wells()[0] + tips1box = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", adapter="opentrons_flex_96_tiprack_adapter") + tips1 = tips1box.wells()[0] + + # Differences between sample types + AL_vol = 230 + TL_vol = 270 + sample_vol = 200 + inc_temp = 55 + starting_vol = AL_vol + sample_vol + binding_buffer_vol = 340 + elution_two_vol = 350 + elution_vol = 100 + + # load 96 channel pipette + pip = ctx.load_instrument("flex_96channel_1000", mount="left") + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + pip.flow_rate.blow_out = 300 + + def resuspend_pellet(vol, plate, reps=3): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=1, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0.75, y=0.75, z=1)) + loc3 = plate.bottom().move(types.Point(x=0, y=1, z=1)) + loc4 = plate.bottom().move(types.Point(x=-0.75, y=0.75, z=1)) + loc5 = plate.bottom().move(types.Point(x=-1, y=0, z=1)) + loc6 = plate.bottom().move(types.Point(x=-0.75, y=0 - 0.75, z=1)) + loc7 = plate.bottom().move(types.Point(x=0, y=-1, z=1)) + loc8 = plate.bottom().move(types.Point(x=0.75, y=-0.75, z=1)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc2) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc3) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc4) + pip.dispense(mixvol, loc4) + pip.aspirate(mixvol, loc5) + pip.dispense(mixvol, loc5) + pip.aspirate(mixvol, loc6) + pip.dispense(mixvol, loc6) + pip.aspirate(mixvol, loc7) + pip.dispense(mixvol, loc7) + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + def bead_mix(vol, plate, reps=5): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=0, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0, y=0, z=8)) + loc3 = plate.bottom().move(types.Point(x=0, y=0, z=16)) + loc4 = plate.bottom().move(types.Point(x=0, y=0, z=24)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc4) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + # Just in case + h_s.close_labware_latch() + + # Start Protocol + pip.pick_up_tip(tips) + # Mix PK and TL buffers + ctx.comment("----- Mixing TL buffer and PK -----") + for m in range(3 if not dry_run else 1): + pip.aspirate(TL_vol, TL_res) + pip.dispense(TL_vol, TL_res.bottom(30)) + # Transfer TL to plate + ctx.comment("----- Transferring TL and PK to samples -----") + pip.aspirate(TL_vol, TL_res) + pip.air_gap(10) + pip.dispense(pip.current_volume, TL_samples) + if not dry_run: + h_s.set_target_temperature(55) + ctx.comment("----- Mixing TL buffer with samples -----") + resuspend_pellet(TL_vol, TL_samples, reps=9 if not dry_run else 1) + pip.return_tip() + + ctx.comment("----- Mixing and incubating for 30 minutes on Heater-Shaker -----") + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=30 if not dry_run else 0.25, msg="Shake at 2000 rpm for 30 minutes to allow lysis.") + h_s.deactivate_shaker() + + # Transfer 200ul of sample + TL buffer to sample plate + ctx.comment("----- Mixing, the transferring 200 ul of sample to new deep well plate -----") + pip.pick_up_tip(tips) + pip.aspirate(sample_vol, TL_samples) + pip.air_gap(20) + pip.dispense(pip.current_volume, samples_m) + pip.blow_out() + pip.return_tip() + + # Move TL samples off H-S into deck slot and sample plate onto H-S + ctx.comment("------- Transferring TL and Sample plates -------") + # Transfer TL samples from H-S to Magnet + h_s.open_labware_latch() + ctx.move_labware(TL_plate, magblock, use_gripper=USE_GRIPPER) + # Move sample plate onto H-S from deck + ctx.move_labware(sample_plate, h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + # Move plate off magplate onto the deck + ctx.move_labware(TL_plate, 6, use_gripper=USE_GRIPPER) + + # Transfer and mix AL_lysis + ctx.comment("------- Starting AL Lysis Steps -------") + pip.pick_up_tip(tips) + pip.aspirate(AL_vol, AL_res) + pip.air_gap(10) + pip.dispense(pip.current_volume, samples_m) + resuspend_pellet(starting_vol, samples_m, reps=9 if not dry_run else 1) + pip.drop_tip(tips) + + # Mix, then heat + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=4 if not dry_run else 0.25, msg="Please wait 4 minutes to allow for proper lysis mixing.") + + h_s.deactivate_shaker() + + # Transfer and mix bind&beads + ctx.comment("------- Mixing and Transferring Beads and Binding -------") + pip.pick_up_tip(tips) + bead_mix(binding_buffer_vol, bind_res, reps=3 if not dry_run else 1) + pip.aspirate(binding_buffer_vol, bind_res) + pip.dispense(binding_buffer_vol, samples_m) + bead_mix(binding_buffer_vol + starting_vol, samples_m, reps=3 if not dry_run else 1) + pip.return_tip() + pip.home() + + # Shake for binding incubation + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please allow 10 minutes for the beads to bind the DNA.") + + h_s.deactivate_shaker() + + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + # Remove Supernatant and move off magnet + ctx.comment("------- Removing Supernatant -------") + pip.pick_up_tip(tips) + pip.aspirate(1000, samples_m.bottom(0.5)) + pip.dispense(1000, bind_res) + if starting_vol + binding_buffer_vol > 1000: + pip.aspirate(1000, samples_m.bottom(0.5)) + pip.dispense(1000, bind_res) + pip.return_tip() + + # Transfer plate from magnet to H/S + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter, use_gripper=USE_GRIPPER) + + h_s.close_labware_latch() + + # Washes + for i in range(num_washes): + if i == 0 or 1: + wash_res = wash1_res + waste_res = TL_res + if i == 2: + wash_res = wash2_res + waste_res = bind_res + ctx.comment("------- Starting Wash #" + str(i + 1) + " -------") + pip.pick_up_tip(tips) + pip.aspirate(wash_vol, wash_res) + pip.dispense(wash_vol, samples_m) + # resuspend_pellet(wash_vol,samples_m,reps=1 if not dry_run else 1) + pip.blow_out() + pip.air_gap(10) + pip.return_tip() + pip.home() + + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=5 if not dry_run else 0.25) + h_s.deactivate_shaker() + h_s.open_labware_latch() + + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + # Remove Supernatant and move off magnet + ctx.comment("------- Removing Supernatant -------") + pip.pick_up_tip(tips) + pip.aspirate(1000, samples_m.bottom(0.5)) + pip.dispense(1000, waste_res.top()) + if wash_vol > 1000: + pip.aspirate(1000, samples_m.bottom(0.5)) + pip.dispense(1000, waste_res.top()) + pip.return_tip() + + # if i == 0 or 2 and not dry_run: + # Transfer plate from magnet to H/S after first two washes + ctx.move_labware(sample_plate, h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + if not dry_run: + dry_beads = 10 + else: + dry_beads = 0.5 + + for beaddry in np.arange(dry_beads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + + # Elution + ctx.comment("------- Beginning Elution Steps -------") + + pip.pick_up_tip(tips1) + pip.aspirate(elution_vol, elution_res) + pip.dispense(elution_vol, samples_m) + resuspend_pellet(elution_vol, samples_m, reps=3 if not dry_run else 1) + pip.return_tip() + pip.home() + + h_s.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please wait 5 minutes to allow dna to elute from beads.") + h_s.deactivate_shaker() + h_s.open_labware_latch() + + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + pip.pick_up_tip(tips1) + pip.aspirate(elution_vol, samples_m) + pip.dispense(elution_vol, elution_res) + pip.return_tip() + + pip.home() diff --git a/app-testing/files/protocols/Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8.py b/app-testing/files/protocols/Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8.py new file mode 100644 index 00000000000..202b01d0f31 --- /dev/null +++ b/app-testing/files/protocols/Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8.py @@ -0,0 +1,405 @@ +from opentrons import protocol_api +from opentrons import types + +metadata = { + "protocolName": "KAPA Library Quant v4.8", + "author": "Opentrons ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.15", +} +# DESCRIPTION +# This protocol is for using KAPA's Illumina Library Quantification Kit to quantify NGS libraries meant for Illumina based Sequencing. + + +# SCRIPT SETTINGS +DRYRUN = False # True = skip incubation times, shorten mix, for testing purposes +TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack + +# PROTOCOL SETTINGS +COLUMNS = 6 # Number of columns of samples in addition to 1 column for Standards maximum 6 (+1 for Standards = 7) +FORMAT = "384" # 96 or 384 +INICOLUMN1 = "A1" # Indicate the initial input columns, for example, Previous NGS library Prep output samples are in wells A10-A12 +INICOLUMN2 = "A2" # Ignore input columns greater than intended number of columns, for example if doing 3 sample columns (plus 1 for Standards), ignore INICOLUMNS4 and up +INICOLUMN3 = "A3" +INICOLUMN4 = "A4" +INICOLUMN5 = "A5" +INICOLUMN6 = "A6" + +# PROTOCOL BLOCKS +STEP_DILUTE = 1 +STEP_MIX = 1 +STEP_DISPENSE = 1 + +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ + +p200_tips = 0 +p50_tips = 0 +Resetcount = 0 + +ABR_TEST = False +if ABR_TEST == True: + DRYRUN = True # Overrides to only DRYRUN + TIP_TRASH = False # Overrides to only REUSING TIPS + RUN = 4 # Repetitions +else: + RUN = 1 + + +def run(protocol: protocol_api.ProtocolContext): + + global p200_tips + global p50_tips + global Resetcount + + protocol.comment("THIS IS A DRY RUN") if DRYRUN == True else protocol.comment("THIS IS A REACTION RUN") + protocol.comment("USED TIPS WILL GO IN TRASH") if TIP_TRASH == True else protocol.comment("USED TIPS WILL BE RE-RACKED") + + # DECK SETUP AND LABWARE + + # ========== FIRST ROW =========== + heatershaker = protocol.load_module("heaterShakerModuleV1", "D1") + hs_adapter = heatershaker.load_adapter("opentrons_96_pcr_adapter") + dilution_plate = hs_adapter.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") + if FORMAT == "96": + qpcrplate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "D2") + if FORMAT == "384": + qpcrplate = protocol.load_labware("appliedbiosystemsmicroamp_384_wellplate_40ul", "D2") + temp_block = protocol.load_module("temperature module gen2", "D3") + temp_adapter = temp_block.load_adapter("opentrons_96_well_aluminum_block") + mix_plate = temp_adapter.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") + # ========== SECOND ROW ========== + mag_block = protocol.load_module("magneticBlockV1", "C1") + source_plate = mag_block.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "C2") + tiprack_50_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "C3") + # ========== THIRD ROW =========== + thermocycler = protocol.load_module("thermocycler module gen2") + reagent_thermo = thermocycler.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") + tiprack_50_2 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "B2") + tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "B3") + # ========== FOURTH ROW ========== + tiprack_50_3 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "A2") + + # REAGENT PLATE + STD = reagent_thermo["A1"] + qPCR = reagent_thermo["A3"] + + # RESERVOIR + DIL = reservoir["A5"] + + # pipette + p1000 = protocol.load_instrument("flex_8channel_1000", "left", tip_racks=[tiprack_200_1]) + p50 = protocol.load_instrument("flex_8channel_50", "right", tip_racks=[tiprack_50_1, tiprack_50_2, tiprack_50_3]) + p1000_flow_rate_aspirate_default = 200 + p1000_flow_rate_dispense_default = 200 + p1000_flow_rate_blow_out_default = 400 + p50_flow_rate_aspirate_default = 50 + p50_flow_rate_dispense_default = 50 + p50_flow_rate_blow_out_default = 100 + + p200_tipracks = 1 + p50_tipracks = 3 + + # samples + + # tip and sample tracking + column_1_list = [INICOLUMN1, INICOLUMN2, INICOLUMN3, INICOLUMN4, INICOLUMN5, INICOLUMN6] + column_DIL1_list = ["A1", "A2", "A3", "A4", "A5", "A6"] + column_DIL2_list = ["A7", "A8", "A9", "A10", "A11", "A12"] + + column_2_list = ["A1", "A2", "A3", "A4", "A5", "A6"] + column_3_list = ["A1", "A2", "A3", "A4", "A5", "A6"] + column_4_list = ["A2", "A3", "A4", "A5", "A6", "A7"] + column_5_list = [ + ["A1", "A2", "B1", "B2"], + ["A3", "A4", "B3", "B4"], + ["A5", "A6", "B5", "B6"], + ["A7", "A8", "B7", "B8"], + ["A9", "A10", "B9", "B10"], + ["A11", "A12", "B11", "B12"], + ["A13", "A14", "B13", "B14"], + ] + + def tipcheck(): + global p200_tips + global p50_tips + global Resetcount + if p200_tips == p200_tipracks * 12: + if ABR_TEST == True: + p1000.reset_tipracks() + else: + protocol.pause("RESET p200 TIPS") + p1000.reset_tipracks() + Resetcount += 1 + p200_tips = 0 + if p50_tips == p50_tipracks * 12: + if ABR_TEST == True: + p50.reset_tipracks() + else: + protocol.pause("RESET p50 TIPS") + p50.reset_tipracks() + Resetcount += 1 + p50_tips = 0 + + ############################################################################################################################################ + ############################################################################################################################################ + ############################################################################################################################################ + + # commands + thermocycler.open_lid() + heatershaker.open_labware_latch() + if DRYRUN == False: + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + thermocycler.set_block_temperature(4) + temp_block.set_temperature(4) + protocol.pause("Ready") + heatershaker.close_labware_latch() + + if STEP_DILUTE == 1: + protocol.comment("==============================================") + protocol.comment("--> Diluting Sample") + protocol.comment("==============================================") + + protocol.comment("--> Adding Diluent") + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default + # =============================================== + tipcheck() + p1000.pick_up_tip() + for loop, X in enumerate(column_1_list): + p1000.aspirate(200, DIL.bottom(z=2)) + p1000.dispense(98, dilution_plate[column_DIL1_list[loop]].bottom(z=0.5)) + p1000.dispense(95, dilution_plate[column_DIL2_list[loop]].bottom(z=0.5)) + p1000.move_to(DIL.top()) + p1000.blow_out() + if loop == COLUMNS - 1: + break + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + protocol.comment("--> Adding Sample to Diluent 1") + SampleVol = 2 + DilMixRPM = 1200 + DilMixTime = 2 * 60 if DRYRUN == False else 0.1 * 60 + # p50.configure_for_volume(2) + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p50.pick_up_tip() + p50.aspirate(SampleVol + 3, source_plate.wells_by_name()[X].bottom(z=1)) + p50.dispense(3, source_plate.wells_by_name()[X].bottom(z=1)) + p50.aspirate(SampleVol + 3, source_plate.wells_by_name()[X].bottom(z=1)) + p50.dispense(3, source_plate.wells_by_name()[X].bottom(z=1)) + p50.dispense(SampleVol + 1, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.mix(2, 10, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.blow_out(dilution_plate.wells_by_name()[column_DIL1_list[loop]].top(z=-2)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + if loop == COLUMNS - 1: + break + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=DilMixRPM) + protocol.delay(DilMixTime) + heatershaker.deactivate_shaker() + + protocol.comment("--> Adding Sample to Diluent 2") + SampleVol = 5 + DilMixRPM = 1200 + DilMixTime = 2 * 60 if DRYRUN == False else 0.1 * 60 + # p50.configure_for_volume(5) + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p50.pick_up_tip() + p50.aspirate(SampleVol + 3, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.dispense(3, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.aspirate(SampleVol + 3, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.dispense(3, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.dispense(SampleVol + 1, dilution_plate.wells_by_name()[column_DIL2_list[loop]].bottom(z=1)) + p50.mix(2, 10, dilution_plate.wells_by_name()[column_DIL2_list[loop]].bottom(z=1)) + p50.blow_out(dilution_plate.wells_by_name()[column_DIL2_list[loop]].top(z=-2)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + if loop == COLUMNS - 1: + break + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=DilMixRPM) + protocol.delay(DilMixTime) + heatershaker.deactivate_shaker() + + for loop in range(RUN): + if STEP_MIX == 1: + protocol.comment("==============================================") + protocol.comment("--> Adding qPCR Mix") + protocol.comment("==============================================") + qPCRVol = 27 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + tipcheck() + p50.pick_up_tip() + for loop, X in enumerate(column_3_list): + p50.aspirate(qPCRVol, qPCR.bottom(z=1)) + p50.dispense(qPCRVol, mix_plate.wells_by_name()[X].bottom(z=0.5)) + p50.default_speed = 50 + p50.move_to(mix_plate.wells_by_name()[X].top(z=-1)) + protocol.delay(seconds=2) + p50.blow_out(mix_plate.wells_by_name()[X].top(z=-1)) + p50.default_speed = 400 + if loop == (COLUMNS + 1) - 1: + break + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + protocol.comment("==============================================") + protocol.comment("--> Adding Standards to Mix") + protocol.comment("==============================================") + SampleVol = 18 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + tipcheck() + p50.pick_up_tip() + p50.aspirate(SampleVol, STD.bottom(z=0.5)) + p50.dispense(SampleVol, mix_plate["A1"].bottom(z=0.5)) + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.mix(5, 30, mix_plate["A1"].bottom(z=1)) + p50.default_speed = 50 + p50.move_to(mix_plate["A1"].top(z=-1)) + protocol.delay(seconds=2) + p50.blow_out(mix_plate["A1"].top(z=-1)) + p50.default_speed = 400 + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + protocol.comment("==============================================") + protocol.comment("--> Adding Diluted Sample to Mix") + protocol.comment("==============================================") + SampleVol = 18 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for loop, X in enumerate(column_2_list): + tipcheck() + p50.pick_up_tip() + p50.aspirate(SampleVol, dilution_plate.wells_by_name()[column_DIL2_list[loop]].bottom(z=1)) + p50.dispense(SampleVol, mix_plate.wells_by_name()[column_4_list[loop]].bottom(z=0.5)) + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.mix(5, 30, mix_plate.wells_by_name()[column_4_list[loop]].bottom(z=1)) + p50.default_speed = 50 + p50.move_to(mix_plate.wells_by_name()[column_4_list[loop]].top(z=-1)) + protocol.delay(seconds=2) + p50.blow_out(mix_plate.wells_by_name()[column_4_list[loop]].top(z=-1)) + p50.default_speed = 400 + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + if loop == (COLUMNS) - 1: + break + # =============================================== + + if STEP_DISPENSE == 1: + if FORMAT == "96": + protocol.comment("==============================================") + protocol.comment("--> Dispensing 96 well") + protocol.comment("==============================================") + MixqPCRVol = 40 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for loop, X in enumerate(column_3_list): + tipcheck() + p50.pick_up_tip() + p50.mix(5, MixqPCRVol - 5, mix_plate[X].bottom(z=1)) + p50.aspirate(MixqPCRVol + 2, mix_plate[X].bottom(z=0.5)) + protocol.delay(seconds=0.2) + # =============================================== + p50.move_to(qpcrplate[X].center()) + p50.default_speed = 100 + p50.dispense(MixqPCRVol, qpcrplate[X].bottom(z=1)) + protocol.delay(seconds=0.2) + p50.move_to(qpcrplate[X].top(z=-1)) + p50.default_speed = 400 + # =============================================== + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + if loop == (COLUMNS + 1) - 1: + break + # =============================================== + + if FORMAT == "384": + protocol.comment("==============================================") + protocol.comment("--> Dispensing 384 well") + protocol.comment("==============================================") + MixqPCRVol = 40 + Multidispense = [10.1, 10.2, 9.8, 9.9] # Slight Volume Changes to account for Multidispense Variation + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for loop, X in enumerate(column_3_list): + tipcheck() + p50.pick_up_tip() + p50.mix(5, MixqPCRVol - 5, mix_plate[X].bottom(z=1)) + p50.aspirate(MixqPCRVol + 5, mix_plate[X].bottom(z=0.5)) + p50.dispense(2, mix_plate[X].bottom(z=0.5)) + protocol.delay(seconds=0.2) + # =============================================== + for loop2, X in enumerate(column_5_list[loop]): + p50.move_to(qpcrplate[X].top(z=1.0)) + protocol.delay(seconds=0.2) + p50.default_speed = 10 + p50.move_to(qpcrplate[X].center()) + p50.default_speed = 2.5 + p50.dispense(Multidispense[loop2], qpcrplate[X].bottom(z=1)) + protocol.delay(seconds=0.2) + p50.default_speed = 100 + # =============================================== + p50.default_speed = 400 + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + if loop == (COLUMNS + 1) - 1: + break + # =============================================== + + if ABR_TEST == True: + protocol.comment("==============================================") + protocol.comment("--> Resetting Run") + protocol.comment("==============================================") + + protocol.comment("--> Refilling qPCR (With Diluent)") + qPCRRefill = qPCRVol * (COLUMNS + 1) + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default + # =============================================== + tipcheck() + p1000.pick_up_tip() + p1000.aspirate(qPCRRefill, DIL.bottom(z=0.5)) + p1000.dispense(qPCRRefill, qPCR.bottom(z=0.5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + protocol.comment("Number of Resets: " + str(Resetcount)) diff --git a/app-testing/files/protocols/Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4.py b/app-testing/files/protocols/Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4.py similarity index 71% rename from app-testing/files/protocols/Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4.py rename to app-testing/files/protocols/Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4.py index 92b7018f773..17682476c40 100644 --- a/app-testing/files/protocols/Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4.py +++ b/app-testing/files/protocols/Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4.py @@ -1,19 +1,15 @@ from opentrons import protocol_api from opentrons import types -metadata = { - "protocolName": "Illumina DNA Enrichment v4", - "author": "Opentrons ", - "source": "Protocol Library", -} +metadata = {"protocolName": "Illumina DNA Enrichment v4", "author": "Opentrons ", "source": "Protocol Library"} requirements = { "robotType": "OT-3", - "apiLevel": "2.15", + "apiLevel": "2.18", } # SCRIPT SETTINGS -DRYRUN = True # True = skip incubation times, shorten mix, for testing purposes +DRYRUN = False # True = skip incubation times, shorten mix, for testing purposes USE_GRIPPER = True # True = Uses Gripper, False = Manual Move TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack HYBRID_PAUSE = True # True = sets a pause on the Hybridization @@ -39,33 +35,61 @@ p200_tips = 0 p50_tips = 0 -ABR_TEST = True -if ABR_TEST == True: - DRYRUN = True # Overrides to only DRYRUN - TIP_TRASH = False # Overrides to only REUSING TIPS - RUN = 3 # Repetitions -else: - RUN = 1 + +TIP_TRASH = False # Overrides to only REUSING TIPS +RUN = 1 + + +def add_parameters(parameters: protocol_api.Parameters): + parameters.add_int( + variable_name="heater_shaker_speed", + display_name="Heater Shaker Shake Speed", + description="Speed to set the heater shaker to", + default=2000, + minimum=200, + maximum=3000, + unit="seconds", + ) + parameters.add_float( + variable_name="dot_bottom", + display_name=".bottom", + description="Lowest value pipette will go to.", + default=0.3, + choices=[ + {"display_name": "0.0", "value": 0.0}, + {"display_name": "0.1", "value": 0.1}, + {"display_name": "0.2", "value": 0.2}, + {"display_name": "0.3", "value": 0.3}, + {"display_name": "0.4", "value": 0.4}, + {"display_name": "0.5", "value": 0.5}, + {"display_name": "0.6", "value": 0.6}, + {"display_name": "0.7", "value": 0.7}, + {"display_name": "0.8", "value": 0.8}, + {"display_name": "0.9", "value": 0.9}, + {"display_name": "1.0", "value": 1.0}, + ], + ) def run(protocol: protocol_api.ProtocolContext): + heater_shaker_speed = protocol.params.heater_shaker_speed + dot_bottom = protocol.params.dot_bottom global p200_tips global p50_tips - if ABR_TEST == True: - protocol.comment("THIS IS A ABR RUN WITH " + str(RUN) + " REPEATS") protocol.comment("THIS IS A DRY RUN") if DRYRUN == True else protocol.comment("THIS IS A REACTION RUN") protocol.comment("USED TIPS WILL GO IN TRASH") if TIP_TRASH == True else protocol.comment("USED TIPS WILL BE RE-RACKED") # DECK SETUP AND LABWARE # ========== FIRST ROW =========== heatershaker = protocol.load_module("heaterShakerModuleV1", "1") - sample_plate_2 = heatershaker.load_labware("nest_96_wellplate_2ml_deep") + sample_plate_2 = heatershaker.load_labware("thermoscientificnunc_96_wellplate_1300ul") + reservoir = protocol.load_labware("nest_96_wellplate_2ml_deep", "2") temp_block = protocol.load_module("temperature module gen2", "3") reagent_plate = temp_block.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") # ========== SECOND ROW ========== - MAG_PLATE_SLOT = 4 + MAG_PLATE_SLOT = protocol.load_module("magneticBlockV1", "C1") tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "5") tiprack_50_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "6") # ========== THIRD ROW =========== @@ -132,84 +156,12 @@ def run(protocol: protocol_api.ProtocolContext): def tipcheck(): if p200_tips >= 3 * 12: - if ABR_TEST == True: - p1000.reset_tipracks() - else: - protocol.pause("RESET p200 TIPS") - p1000.reset_tipracks() + p1000.reset_tipracks() p200_tips == 0 if p50_tips >= 2 * 12: - if ABR_TEST == True: - p50.reset_tipracks() - else: - protocol.pause("RESET p50 TIPS") - p50.reset_tipracks() + p50.reset_tipracks() p50_tips == 0 - def grip_offset(action, item, slot=None): - """Grip offset.""" - from opentrons.types import Point - - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _pick_up_offsets = { - "deck": Point(), - "mag-plate": Point(), - "heater-shaker": Point(z=1.0), - "temp-module": Point(), - "thermo-cycler": Point(), - } - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _drop_offsets = { - "deck": Point(), - "mag-plate": Point(x=0.1, y=-0.25, z=0.5), - "heater-shaker": Point(y=-0.5), - "temp-module": Point(), - "thermo-cycler": Point(), - } - # do NOT edit these values - # NOTE: these values will eventually be in our software - # and will not need to be inside a protocol - _hw_offsets = { - "deck": Point(), - "mag-plate": Point(z=34.5), - "heater-shaker-right": Point(z=2.5), - "heater-shaker-left": Point(z=2.5), - "temp-module": Point(z=5.0), - "thermo-cycler": Point(z=2.5), - } - # make sure arguments are correct - action_options = ["pick-up", "drop"] - item_options = list(_hw_offsets.keys()) - item_options.remove("heater-shaker-left") - item_options.remove("heater-shaker-right") - item_options.append("heater-shaker") - if action not in action_options: - raise ValueError(f'"{action}" not recognized, available options: {action_options}') - if item not in item_options: - raise ValueError(f'"{item}" not recognized, available options: {item_options}') - if item == "heater-shaker": - assert slot, 'argument slot= is required when using "heater-shaker"' - if slot in [1, 4, 7, 10]: - side = "left" - elif slot in [3, 6, 9, 12]: - side = "right" - else: - raise ValueError("heater shaker must be on either left or right side") - hw_offset = _hw_offsets[f"{item}-{side}"] - else: - hw_offset = _hw_offsets[item] - if action == "pick-up": - offset = hw_offset + _pick_up_offsets[item] - else: - offset = hw_offset + _drop_offsets[item] - - # convert from Point() to dict() - return {"x": offset.x, "y": offset.y, "z": offset.z} - ############################################################################################################################################ ############################################################################################################################################ ############################################################################################################################################ @@ -248,8 +200,8 @@ def grip_offset(action, item, slot=None): NHB2Vol = 50 for loop, X in enumerate(column_1_list): p50.pick_up_tip() - p50.aspirate(NHB2Vol, NHB2.bottom()) - p50.dispense(NHB2Vol, sample_plate_1[X].bottom()) + p50.aspirate(NHB2Vol, NHB2.bottom(z=dot_bottom)) # original = () + p50.dispense(NHB2Vol, sample_plate_1[X].bottom(z=dot_bottom)) # original = () p50.return_tip() if TIP_TRASH == False else p50.drop_tip() p50_tips += 1 tipcheck() @@ -258,8 +210,8 @@ def grip_offset(action, item, slot=None): PanelVol = 10 for loop, X in enumerate(column_1_list): p50.pick_up_tip() - p50.aspirate(PanelVol, Panel.bottom()) - p50.dispense(PanelVol, sample_plate_1[X].bottom()) + p50.aspirate(PanelVol, Panel.bottom(z=dot_bottom)) # original = () + p50.dispense(PanelVol, sample_plate_1[X].bottom(z=dot_bottom)) # original = () p50.return_tip() if TIP_TRASH == False else p50.drop_tip() p50_tips += 1 tipcheck() @@ -270,9 +222,9 @@ def grip_offset(action, item, slot=None): EHB2MixVol = 90 for loop, X in enumerate(column_1_list): p1000.pick_up_tip() - p1000.aspirate(EHB2Vol, EHB2.bottom()) - p1000.dispense(EHB2Vol, sample_plate_1[X].bottom()) - p1000.move_to(sample_plate_1[X].bottom()) + p1000.aspirate(EHB2Vol, EHB2.bottom(z=dot_bottom)) # original = () + p1000.dispense(EHB2Vol, sample_plate_1[X].bottom(z=dot_bottom)) # original = () + p1000.move_to(sample_plate_1[X].bottom(z=dot_bottom)) # original = () p1000.mix(EHB2MixRep, EHB2MixVol) p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() p50_tips += 1 @@ -333,9 +285,9 @@ def grip_offset(action, item, slot=None): TransferSup = 100 for loop, X in enumerate(column_1_list): p1000.pick_up_tip() - p1000.move_to(sample_plate_1[X].bottom(z=0.25)) + p1000.move_to(sample_plate_1[X].bottom(z=0.5)) p1000.aspirate(TransferSup + 1, rate=0.25) - p1000.dispense(TransferSup + 5, sample_plate_2[column_2_list[loop]].bottom(z=1)) + p1000.dispense(TransferSup + 1, sample_plate_2[column_2_list[loop]].bottom(z=1)) p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() p200_tips += 1 tipcheck() @@ -345,7 +297,7 @@ def grip_offset(action, item, slot=None): protocol.comment("--> ADDING SMB") SMBVol = 250 SampleVol = 100 - SMBMixRPM = 2000 + SMBMixRPM = heater_shaker_speed SMBMixRep = 5 * 60 if DRYRUN == False else 0.1 * 60 SMBPremix = 3 if DRYRUN == False else 1 # ============================== @@ -382,13 +334,7 @@ def grip_offset(action, item, slot=None): # ============================================================================================ # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) + protocol.move_labware(labware=sample_plate_2, new_location=MAG_PLATE_SLOT, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -424,13 +370,7 @@ def grip_offset(action, item, slot=None): # ============================================================================================ # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) + protocol.move_labware(labware=sample_plate_2, new_location=heatershaker, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -438,17 +378,18 @@ def grip_offset(action, item, slot=None): washreps = 3 washcount = 0 for wash in range(washreps): + protocol.comment("--> Adding EEW") EEWVol = 200 for loop, X in enumerate(column_2_list): p1000.pick_up_tip() - p1000.aspirate(EEWVol, WASHES[loop].bottom()) - p1000.dispense(EEWVol, sample_plate_2[X].bottom()) + p1000.aspirate(EEWVol, WASHES[loop].bottom(z=dot_bottom)) # original = () + p1000.dispense(EEWVol, sample_plate_2[X].bottom(z=dot_bottom)) # original = () p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() p200_tips += 1 tipcheck() heatershaker.close_labware_latch() - heatershaker.set_and_wait_for_shake_speed(rpm=1800) + heatershaker.set_and_wait_for_shake_speed(rpm=(heater_shaker_speed * 0.9)) if DRYRUN == False: protocol.delay(seconds=4 * 60) heatershaker.deactivate_shaker() @@ -460,13 +401,7 @@ def grip_offset(action, item, slot=None): # ============================================================================================ # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) + protocol.move_labware(labware=sample_plate_2, new_location=MAG_PLATE_SLOT, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -497,13 +432,7 @@ def grip_offset(action, item, slot=None): # ============================================================================================ # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) + protocol.move_labware(labware=sample_plate_2, new_location=heatershaker, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -513,13 +442,13 @@ def grip_offset(action, item, slot=None): EEWVol = 200 for loop, X in enumerate(column_2_list): p1000.pick_up_tip() - p1000.aspirate(EEWVol, WASHES[loop].bottom()) - p1000.dispense(EEWVol, sample_plate_2[X].bottom()) + p1000.aspirate(EEWVol, WASHES[loop].bottom(z=dot_bottom)) # original = () + p1000.dispense(EEWVol, sample_plate_2[X].bottom(z=dot_bottom)) # original = () p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() p200_tips += 1 tipcheck() - heatershaker.set_and_wait_for_shake_speed(rpm=1800) + heatershaker.set_and_wait_for_shake_speed(rpm=(heater_shaker_speed * 0.9)) if DRYRUN == False: protocol.delay(seconds=4 * 60) heatershaker.deactivate_shaker() @@ -531,7 +460,7 @@ def grip_offset(action, item, slot=None): TransferSup = 200 for loop, X in enumerate(column_2_list): p1000.pick_up_tip() - p1000.move_to(sample_plate_2[X].bottom(z=0.25)) + p1000.move_to(sample_plate_2[X].bottom(z=0.5)) p1000.aspirate(TransferSup, rate=0.25) p1000.dispense(TransferSup, sample_plate_2[column_3_list[loop]].bottom(z=1)) p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() @@ -544,13 +473,7 @@ def grip_offset(action, item, slot=None): # ============================================================================================ # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) + protocol.move_labware(labware=sample_plate_2, new_location=MAG_PLATE_SLOT, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -558,7 +481,7 @@ def grip_offset(action, item, slot=None): protocol.delay(seconds=1 * 60) protocol.comment("--> Removing Supernatant") - RemoveSup = 150 + RemoveSup = 200 for loop, X in enumerate(column_3_list): p1000.pick_up_tip() p1000.move_to(sample_plate_2[X].bottom(z=3.5)) @@ -578,10 +501,10 @@ def grip_offset(action, item, slot=None): protocol.comment("--> Removing Residual") for loop, X in enumerate(column_3_list): p50.pick_up_tip() - p50.move_to(sample_plate_2[X].bottom(z=0)) + p50.move_to(sample_plate_2[X].bottom(z=dot_bottom)) # original = z=0 p50.aspirate(50, rate=0.25) p50.default_speed = 200 - p50.dispense(100, Liquid_trash.top(z=-7)) + p50.dispense(50, Liquid_trash.top(z=-7)) protocol.delay(minutes=0.1) p50.blow_out() p50.default_speed = 400 @@ -599,8 +522,8 @@ def grip_offset(action, item, slot=None): EluteVol = 23 for loop, X in enumerate(column_3_list): p50.pick_up_tip() - p50.aspirate(EluteVol, Elute.bottom()) - p50.dispense(EluteVol, sample_plate_2[X].bottom()) + p50.aspirate(EluteVol, Elute.bottom(z=dot_bottom)) # original = () + p50.dispense(EluteVol, sample_plate_2[X].bottom(z=dot_bottom)) # original = () p50.return_tip() if TIP_TRASH == False else p50.drop_tip() p50_tips += 1 tipcheck() @@ -608,18 +531,12 @@ def grip_offset(action, item, slot=None): # ============================================================================================ # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) + protocol.move_labware(labware=sample_plate_2, new_location=heatershaker, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ heatershaker.close_labware_latch() - heatershaker.set_and_wait_for_shake_speed(rpm=1800) + heatershaker.set_and_wait_for_shake_speed(rpm=(heater_shaker_speed * 0.9)) if DRYRUN == False: protocol.delay(seconds=2 * 60) heatershaker.deactivate_shaker() @@ -631,13 +548,7 @@ def grip_offset(action, item, slot=None): # ============================================================================================ # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) + protocol.move_labware(labware=sample_plate_2, new_location=MAG_PLATE_SLOT, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -645,9 +556,9 @@ def grip_offset(action, item, slot=None): TransferSup = 21 for loop, X in enumerate(column_3_list): p50.pick_up_tip() - p50.move_to(sample_plate_2[X].bottom(z=0.25)) + p50.move_to(sample_plate_2[X].bottom(z=0.5)) p50.aspirate(TransferSup + 1, rate=0.25) - p50.dispense(TransferSup + 5, sample_plate_1[column_4_list[loop]].bottom(z=1)) + p50.dispense(TransferSup + 1, sample_plate_1[column_4_list[loop]].bottom(z=1)) p50.return_tip() if TIP_TRASH == False else p50.drop_tip() p50_tips += 1 tipcheck() @@ -658,9 +569,9 @@ def grip_offset(action, item, slot=None): ET2MixVol = 20 for loop, X in enumerate(column_4_list): p50.pick_up_tip() - p50.aspirate(ET2Vol, ET2.bottom()) - p50.dispense(ET2Vol, sample_plate_1[X].bottom()) - p50.move_to(sample_plate_1[X].bottom()) + p50.aspirate(ET2Vol, ET2.bottom(z=dot_bottom)) # original = () + p50.dispense(ET2Vol, sample_plate_1[X].bottom(z=dot_bottom)) # original = () + p50.move_to(sample_plate_1[X].bottom(z=dot_bottom)) # original = () p50.mix(ET2MixRep, ET2MixVol) p50.return_tip() if TIP_TRASH == False else p50.drop_tip() p50_tips += 1 @@ -675,8 +586,8 @@ def grip_offset(action, item, slot=None): PPCVol = 5 for loop, X in enumerate(column_4_list): p50.pick_up_tip() - p50.aspirate(PPCVol, PPC.bottom()) - p50.dispense(PPCVol, sample_plate_1[X].bottom()) + p50.aspirate(PPCVol, PPC.bottom(z=dot_bottom)) # original = () + p50.dispense(PPCVol, sample_plate_1[X].bottom(z=dot_bottom)) # original = () p50.return_tip() if TIP_TRASH == False else p50.drop_tip() p50_tips += 1 tipcheck() @@ -687,9 +598,9 @@ def grip_offset(action, item, slot=None): EPMMixVol = 45 for loop, X in enumerate(column_4_list): p50.pick_up_tip() - p50.aspirate(EPMVol, EPM.bottom()) - p50.dispense(EPMVol, sample_plate_1[X].bottom()) - p50.move_to(sample_plate_1[X].bottom()) + p50.aspirate(EPMVol, EPM.bottom(z=dot_bottom)) # original = () + p50.dispense(EPMVol, sample_plate_1[X].bottom(z=dot_bottom)) # original = () + p50.move_to(sample_plate_1[X].bottom(z=dot_bottom)) # original = () p50.mix(EPMMixRep, EPMMixVol) p50.return_tip() if TIP_TRASH == False else p50.drop_tip() p50_tips += 1 @@ -726,13 +637,7 @@ def grip_offset(action, item, slot=None): # ============================================================================================ # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) + protocol.move_labware(labware=sample_plate_2, new_location=heatershaker, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -740,9 +645,9 @@ def grip_offset(action, item, slot=None): TransferSup = 45 for loop, X in enumerate(column_4_list): p50.pick_up_tip() - p50.move_to(sample_plate_1[X].bottom(z=0.25)) + p50.move_to(sample_plate_1[X].bottom(z=0.5)) p50.aspirate(TransferSup + 1, rate=0.25) - p50.dispense(TransferSup + 5, sample_plate_2[column_5_list[loop]].bottom(z=1)) + p50.dispense(TransferSup + 1, sample_plate_2[column_5_list[loop]].bottom(z=1)) p50.return_tip() if TIP_TRASH == False else p50.drop_tip() p50_tips += 1 tipcheck() @@ -779,20 +684,14 @@ def grip_offset(action, item, slot=None): p200_tips += 1 tipcheck() # ========NEW HS MIX========================= - heatershaker.set_and_wait_for_shake_speed(rpm=1800) + heatershaker.set_and_wait_for_shake_speed(rpm=(heater_shaker_speed * 0.9)) protocol.delay(AMPureMixRep) heatershaker.deactivate_shaker() # ============================================================================================ # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) + protocol.move_labware(labware=sample_plate_2, new_location=MAG_PLATE_SLOT, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -871,10 +770,10 @@ def grip_offset(action, item, slot=None): protocol.comment("--> Removing Residual ETOH") for loop, X in enumerate(column_5_list): p1000.pick_up_tip() - p1000.move_to(sample_plate_2[X].bottom(z=0)) + p1000.move_to(sample_plate_2[X].bottom(z=dot_bottom)) # original = (z=0) p1000.aspirate(50, rate=0.25) p1000.default_speed = 200 - p1000.dispense(100, Liquid_trash.top(z=-7)) + p1000.dispense(50, Liquid_trash.top(z=-7)) protocol.delay(minutes=0.1) p1000.blow_out() p1000.default_speed = 400 @@ -890,13 +789,7 @@ def grip_offset(action, item, slot=None): # ============================================================================================ # GRIPPER MOVE PLATE FROM MAG PLATE TO HEATER SHAKER heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) + protocol.move_labware(labware=sample_plate_2, new_location=heatershaker, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -933,20 +826,14 @@ def grip_offset(action, item, slot=None): p200_tips += 1 tipcheck() if DRYRUN == False: - heatershaker.set_and_wait_for_shake_speed(rpm=1600) + heatershaker.set_and_wait_for_shake_speed(rpm=(heater_shaker_speed * 0.8)) protocol.delay(RSBMixRep) heatershaker.deactivate_shaker() # ============================================================================================ # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) + protocol.move_labware(labware=sample_plate_2, new_location=MAG_PLATE_SLOT, use_gripper=USE_GRIPPER) heatershaker.close_labware_latch() # ============================================================================================ @@ -957,110 +844,9 @@ def grip_offset(action, item, slot=None): TransferSup = 30 for loop, X in enumerate(column_5_list): p1000.pick_up_tip() - p1000.move_to(sample_plate_2[X].bottom(z=0.25)) + p1000.move_to(sample_plate_2[X].bottom(z=0.5)) p1000.aspirate(TransferSup + 1, rate=0.25) - p1000.dispense(TransferSup + 5, sample_plate_1[column_6_list[loop]].bottom(z=1)) + p1000.dispense(TransferSup + 1, sample_plate_1[column_6_list[loop]].bottom(z=1)) p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() p200_tips += 1 tipcheck() - - if ABR_TEST == True: - protocol.comment("==============================================") - protocol.comment("--> Resetting Run") - protocol.comment("==============================================") - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM MAG PLATE TO HEATER SHAKER - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - p1000.pick_up_tip() - # Resetting NHB2 - p1000.aspirate(COLUMNS * 50, Liquid_trash_well_1.bottom(z=1)) - p1000.dispense(COLUMNS * 50, NHB2.bottom(z=1)) - # Resetting Panel - p1000.aspirate(COLUMNS * 10, Liquid_trash_well_1.bottom(z=1)) - p1000.dispense(COLUMNS * 10, Panel.bottom(z=1)) - # Resetting EHB2 - p1000.aspirate(COLUMNS * 10, Liquid_trash_well_1.bottom(z=1)) - p1000.dispense(COLUMNS * 10, EHB2.bottom(z=1)) - # Resetting SMB - for X in range(COLUMNS): - p1000.aspirate(125, Liquid_trash_well_1.bottom(z=1)) - p1000.dispense(125, SMB.bottom(z=1)) - p1000.aspirate(125, Liquid_trash_well_1.bottom(z=1)) - p1000.dispense(125, SMB.bottom(z=1)) - - # Resetting TWB - for X in range(COLUMNS): - p1000.aspirate(200, Liquid_trash_well_2.bottom(z=1)) - p1000.dispense(200, EEW_1.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_2.bottom(z=1)) - p1000.dispense(200, EEW_1.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_2.bottom(z=1)) - p1000.dispense(200, EEW_2.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_2.bottom(z=1)) - p1000.dispense(200, EEW_2.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_2.bottom(z=1)) - p1000.dispense(200, EEW_3.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_2.bottom(z=1)) - p1000.dispense(200, EEW_3.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_3.bottom(z=1)) - p1000.dispense(200, EEW_1.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_3.bottom(z=1)) - p1000.dispense(200, EEW_1.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_3.bottom(z=1)) - p1000.dispense(200, EEW_2.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_3.bottom(z=1)) - p1000.dispense(200, EEW_2.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_3.bottom(z=1)) - p1000.dispense(200, EEW_3.bottom(z=1)) - p1000.aspirate(200, Liquid_trash_well_3.bottom(z=1)) - p1000.dispense(200, EEW_3.bottom(z=1)) - # Resetting ETOH - for X in range(COLUMNS): - p1000.aspirate(150, Liquid_trash_well_4.bottom(z=1)) - p1000.dispense(150, EtOH.bottom(z=1)) - p1000.aspirate(150, Liquid_trash_well_4.bottom(z=1)) - p1000.dispense(150, EtOH.bottom(z=1)) - # Resetting AMPURE - for X in range(COLUMNS): - p1000.aspirate(COLUMNS * 40.5, Liquid_trash_well_4.bottom(z=1)) - p1000.dispense(COLUMNS * 40.5, AMPure.bottom(z=1)) - # Resetting Elute - p1000.aspirate(COLUMNS * 25, Liquid_trash_well_4.bottom(z=1)) - p1000.dispense(COLUMNS * 25, Elute.bottom(z=1)) - # Resetting EPM - p1000.aspirate(COLUMNS * 40, Liquid_trash_well_4.bottom(z=1)) - p1000.dispense(COLUMNS * 40, EPM.bottom(z=1)) - p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() - p200_tips += 1 - tipcheck() - - p50.pick_up_tip() - # Resetting ET2 - p50.aspirate(COLUMNS * 4, Liquid_trash_well_4.bottom(z=1)) - p50.dispense(COLUMNS * 4, ET2.bottom(z=1)) - # Resetting PPC - p50.aspirate(COLUMNS * 5, Liquid_trash_well_4.bottom(z=1)) - p50.dispense(COLUMNS * 5, PPC.bottom(z=1)) - # Removing Final Samples - for loop, X in enumerate(column_6_list): - p50.aspirate(32, sample_plate_1[X].bottom(z=1)) - p50.dispense(32, Liquid_trash_well_4.bottom(z=1)) - # Resetting Samples - for loop, X in enumerate(column_1_list): - p50.aspirate(30, Liquid_trash_well_4.bottom(z=1)) - p50.dispense(30, sample_plate_1[X].bottom(z=1)) - - p50.return_tip() if TIP_TRASH == False else p50.drop_tip() - p50_tips += 1 - tipcheck() diff --git a/app-testing/files/protocols/Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7.py b/app-testing/files/protocols/Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7.py new file mode 100644 index 00000000000..059e66149be --- /dev/null +++ b/app-testing/files/protocols/Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7.py @@ -0,0 +1,824 @@ +from opentrons import protocol_api +from opentrons import types +import threading + +metadata = { + "protocolName": "Illumina DNA Prep 24x v4.7", + "author": "Opentrons ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.18", +} + +# SCRIPT SETTINGS +USE_GRIPPER = True # True = Uses Gripper, False = Manual Move +TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack + +# PROTOCOL SETTINGS +COLUMNS = 3 # 1-4 +PCRCYCLES = 7 # Amount of PCR cycles +RES_TYPE = "12x15ml" # '12x15ml' or '96x2ml' +ETOH_AirMultiDis = True +RSB_AirMultiDis = True + +# PROTOCOL BLOCKS +STEP_TAG = 1 +STEP_WASH = 1 +STEP_PCRDECK = 1 +STEP_CLEANUP = 1 + +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ + +p200_tips = 0 +p50_tips = 0 +WasteVol = 0 +Resetcount = 0 + +TIP_TRASH = False # Overrides to only REUSING TIPS +RUN = 1 # Repetitions + + +def add_parameters(parameters: protocol_api.Parameters): + parameters.add_int( + variable_name="heater_shaker_speed", + display_name="Heater Shaker Shake Speed", + description="Speed to set the heater shaker to", + default=2000, + minimum=200, + maximum=3000, + unit="rpm", + ) + parameters.add_str( + variable_name="mount_pos_50", + display_name="8ch 50 ul Mount Position", + description="What mount to use", + choices=[ + {"display_name": "left_mount", "value": "left"}, + {"display_name": "right_mount", "value": "right"}, + ], + default="right", + ) + parameters.add_str( + variable_name="mount_pos_1000", + display_name="8ch 1000 ul Mount Position", + description="What mount to use", + choices=[ + {"display_name": "left_mount", "value": "left"}, + {"display_name": "right_mount", "value": "right"}, + ], + default="left", + ) + parameters.add_float( + variable_name="dot_bottom", + display_name=".bottom", + description="Lowest value pipette will go to.", + default=0.5, + choices=[ + {"display_name": "0.0", "value": 0.0}, + {"display_name": "0.1", "value": 0.1}, + {"display_name": "0.2", "value": 0.2}, + {"display_name": "0.3", "value": 0.3}, + {"display_name": "0.4", "value": 0.4}, + {"display_name": "0.5", "value": 0.5}, + {"display_name": "0.6", "value": 0.6}, + {"display_name": "0.7", "value": 0.7}, + {"display_name": "0.8", "value": 0.8}, + {"display_name": "0.9", "value": 0.9}, + {"display_name": "1.0", "value": 1.0}, + ], + ) + parameters.add_int( + variable_name="temp_mod_timeout", + display_name="Temp Mod Max time to 4 C (sec)", + description="Max time protocol should wait for temperature module to reach 4C.", + default=3600, + minimum=60, + maximum=7200, + unit="sec", + ) + + +def run(protocol: protocol_api.ProtocolContext): + heater_shaker_speed = protocol.params.heater_shaker_speed + mount_pos_1000 = protocol.params.mount_pos_1000 + mount_pos_50 = protocol.params.mount_pos_50 + bottom_val = protocol.params.dot_bottom + temp_mod_timeout = protocol.params.temp_mod_timeout + global p200_tips + global p50_tips + global WasteVol + global Resetcount + protocol.comment("USED TIPS WILL GO IN TRASH") if TIP_TRASH == True else protocol.comment("USED TIPS WILL BE RE-RACKED") + + # DECK SETUP AND LABWARE + # ========== FIRST ROW =========== + heatershaker = protocol.load_module("heaterShakerModuleV1", "D1") + hs_adapter = heatershaker.load_adapter("opentrons_96_pcr_adapter") + sample_plate_1 = hs_adapter.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") + if RES_TYPE == "12x15ml": + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "D2") + if RES_TYPE == "96x2ml": + reservoir = protocol.load_labware("nest_96_wellplate_2ml_deep", "D2") + temp_block = protocol.load_module("temperature module gen2", "D3") + temp_adapter = temp_block.load_adapter("opentrons_96_well_aluminum_block") + reagent_plate = temp_adapter.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") + # ========== SECOND ROW ========== + mag_block = protocol.load_module("magneticBlockV1", "C1") + tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "C2") + tiprack_50_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "C3") + # ========== THIRD ROW =========== + thermocycler = protocol.load_module("thermocycler module gen2") + tiprack_200_2 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "B2") + tiprack_50_2 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "B3") + # ========== FOURTH ROW ========== + tiprack_200_3 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "A2") + + # =========== RESERVOIR ========== + AMPure = reservoir["A1"] + TAGSTOP = reservoir["A2"] + TWB = reservoir["A4"] + EtOH = reservoir["A6"] + Liquid_trash_well_1 = reservoir["A11"] + Liquid_trash_well_2 = reservoir["A12"] + + # ========= REAGENT PLATE ========= + TAGMIX = reagent_plate["A1"] + EPM = reagent_plate["A2"] + H20 = reagent_plate["A3"] + RSB = reagent_plate["A4"] + Barcodes_1 = reagent_plate["A7"] + Barcodes_2 = reagent_plate["A8"] + Barcodes_3 = reagent_plate["A9"] + Barcodes_4 = reagent_plate["A10"] + + # pipette + p1000 = protocol.load_instrument("flex_8channel_1000", mount_pos_1000, tip_racks=[tiprack_200_1, tiprack_200_2, tiprack_200_3]) + p50 = protocol.load_instrument("flex_8channel_50", mount_pos_50, tip_racks=[tiprack_50_1, tiprack_50_2]) + p200_tipracks = 3 + p50_tipracks = 2 + + # tip and sample tracking + if COLUMNS == 1: + column_1_list = ["A1"] + column_2_list = ["A5"] + column_3_list = ["A9"] + barcodes = ["A7"] + if COLUMNS == 2: + column_1_list = ["A1", "A2"] + column_2_list = ["A5", "A6"] + column_3_list = ["A9", "A10"] + barcodes = ["A7", "A8"] + if COLUMNS == 3: + column_1_list = ["A1", "A2", "A3"] + column_2_list = ["A5", "A6", "A7"] + column_3_list = ["A9", "A10", "A11"] + barcodes = ["A7", "A8", "A9"] + if COLUMNS == 4: + column_1_list = ["A1", "A2", "A3", "A4"] + column_2_list = ["A5", "A6", "A7", "A8"] + column_3_list = ["A9", "A10", "A11", "A12"] + barcodes = ["A7", "A8", "A9", "A10"] + + def tipcheck(): + global p200_tips + global p50_tips + global Resetcount + if p200_tips == p200_tipracks * 12: + if TIP_TRASH == False: + p1000.reset_tipracks() + else: + protocol.pause("RESET p200 TIPS") + p1000.reset_tipracks() + Resetcount += 1 + p200_tips = 0 + if p50_tips == p50_tipracks * 12: + if TIP_TRASH == False: + p50.reset_tipracks() + else: + protocol.pause("RESET p50 TIPS") + p50.reset_tipracks() + Resetcount += 1 + p50_tips = 0 + + def DispWasteVol(Vol): + global WasteVol + WasteVol += int(Vol) + if WasteVol < 1200: + return Liquid_trash_well_1 + if WasteVol > 1200: + return Liquid_trash_well_2 + + ############################################################################################################################################ + ############################################################################################################################################ + ############################################################################################################################################ + # commands + for loop in range(RUN): + thermocycler.open_lid() + heatershaker.open_labware_latch() + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + thermocycler.set_block_temperature(4) + thermocycler.set_lid_temperature(100) + + def set_temperature_with_timeout(temp_block, timeout): + def set_temperature(): + temp_block.set_temperature(4) + + # Create a thread to run the set_temperature function + thread = threading.Thread(target=set_temperature) + thread.start() + thread.join(timeout) + + if thread.is_alive(): + raise RuntimeError(f"Temperature module timeout. Took longer than {timeout} seconds to reach 4 C. Protocol terminated.") + + try: + set_temperature_with_timeout(temp_block, temp_mod_timeout) + except RuntimeError as e: + protocol.comment(str(e)) + raise + + protocol.comment("Ready") + heatershaker.close_labware_latch() + + # Sample Plate contains 50ng of DNA in 30ul Low EDTA TE + + if STEP_TAG == 1: + protocol.comment("==============================================") + protocol.comment("--> Tagment") + protocol.comment("==============================================") + + protocol.comment("--> ADDING TAGMIX") + TagVol = 20 + SampleVol = 50 + TagMixTime = 5 * 60 + TagPremix = 3 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p1000.pick_up_tip() + p1000.mix(TagPremix, TagVol + 10, TAGMIX.bottom(z=1)) + p1000.aspirate(TagVol + 3, TAGMIX.bottom(z=1), rate=0.25) + p1000.dispense(3, TAGMIX.bottom(z=1), rate=0.25) + p1000.dispense(TagVol, sample_plate_1[X].bottom(z=1), rate=0.25) + p1000.mix(2, SampleVol, sample_plate_1[X].bottom(z=0.75)) + p1000.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(minutes=0.1) + p1000.blow_out(sample_plate_1[X].top(z=-3)) + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.move_to(sample_plate_1[X].top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=heater_shaker_speed * 0.8) + protocol.delay(TagMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATERSHAKER TO THERMOCYCLER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=thermocycler, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + ############################################################################################################################################ + thermocycler.close_lid() + profile_TAG = [{"temperature": 55, "hold_time_minutes": 15}] + thermocycler.execute_profile(steps=profile_TAG, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(10) + thermocycler.open_lid() + ############################################################################################################################################ + + protocol.comment("--> Adding TAGSTOP") + TAGSTOPVol = 10 + TAGSTOPMixRep = 10 + TAGSTOPMixVol = 20 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p50.pick_up_tip() + p50.aspirate(TAGSTOPVol + 3, TAGSTOP.bottom(z=bottom_val)) # original = () + p50.dispense(3, TAGSTOP.bottom(z=bottom_val)) # original = () + p50.dispense(TAGSTOPVol, sample_plate_1[X].bottom(z=bottom_val)) # original = () + p50.move_to(sample_plate_1[X].bottom(z=bottom_val)) # original = () + p50.mix(TAGSTOPMixRep, TAGSTOPMixVol) + p50.blow_out(sample_plate_1[X].top(z=-2)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + ############################################################################################################################################ + thermocycler.close_lid() + profile_TAGSTOP = [{"temperature": 37, "hold_time_minutes": 15}] + thermocycler.execute_profile(steps=profile_TAGSTOP, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(10) + thermocycler.open_lid() + ############################################################################################################################################ + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM THERMOCYCLER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + protocol.comment("SETTING THERMO to Room Temp") + thermocycler.set_block_temperature(20) + thermocycler.set_lid_temperature(37) + + protocol.delay(minutes=4) + + if STEP_WASH == 1: + protocol.comment("==============================================") + protocol.comment("--> Wash") + protocol.comment("==============================================") + # Setting Labware to Resume at Wash + if STEP_TAG == 0: + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATERSHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_1[X].bottom(z=0.75)) + p1000.aspirate(100, rate=0.25) + p1000.move_to(sample_plate_1[X].top(z=-2)) + Liquid_trash = DispWasteVol(60) + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=0)) + p1000.aspirate(20) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + for X in range(3): + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM MAG PLATE TO HEATER SHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Wash ") + TWBMaxVol = 100 + TWBTime = 3 * 60 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p1000.pick_up_tip() + p1000.aspirate(TWBMaxVol + 3, TWB.bottom(z=1), rate=0.25) + p1000.dispense(3, TWB.bottom(z=1), rate=0.25) + p1000.move_to(sample_plate_1[X].bottom(z=1)) + p1000.dispense(TWBMaxVol, rate=0.25) + p1000.mix(2, 90, rate=0.5) + p1000.move_to(sample_plate_1[X].top(z=1)) + protocol.delay(minutes=0.1) + p1000.blow_out(sample_plate_1[X].top(z=1)) + p1000.aspirate(20) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATER SHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.delay(minutes=3) + + protocol.comment("--> Remove Wash") + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(4)) + p1000.aspirate(TWBMaxVol, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].bottom(z=bottom_val)) # original = () + protocol.delay(minutes=0.1) + p1000.aspirate(200 - TWBMaxVol, rate=0.25) + p1000.default_speed = 400 + Liquid_trash = DispWasteVol(100) + p1000.dispense(200, Liquid_trash) + p1000.move_to(Liquid_trash.top(z=5)) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=5)) + p1000.aspirate(20) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual Wash") + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p50.pick_up_tip() + p50.move_to(sample_plate_1[X].bottom(1)) + p50.aspirate(20, rate=0.25) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM MAG PLATE TO HEATER SHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Adding EPM") + EPMVol = 40 + EPMMixTime = 3 * 60 + EPMMixRPM = 2000 + EPMMixVol = 35 + EPMVolCount = 0 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p50.pick_up_tip() + p50.aspirate(EPMVol + 3, EPM.bottom(z=1)) + p50.dispense(3, EPM.bottom(z=1)) + EPMVolCount += 1 + p50.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=1.3 * 0.8, y=0, z=-4)))) + p50.dispense(EPMMixVol, rate=1) + p50.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) + p50.aspirate(EPMMixVol, rate=1) + p50.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=0, y=1.3 * 0.8, z=-4)))) + p50.dispense(EPMMixVol, rate=1) + p50.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) + p50.aspirate(EPMMixVol, rate=1) + p50.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=1.3 * -0.8, y=0, z=-4)))) + p50.dispense(EPMMixVol, rate=1) + p50.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) + p50.aspirate(EPMMixVol, rate=1) + p50.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=0, y=1.3 * -0.8, z=-4)))) + p50.dispense(EPMMixVol, rate=1) + p50.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) + p50.aspirate(EPMMixVol, rate=1) + p50.dispense(EPMMixVol, rate=1) + p50.blow_out(sample_plate_1.wells_by_name()[X].center()) + p50.move_to(sample_plate_1.wells_by_name()[X].bottom(z=bottom_val)) + p50.move_to(sample_plate_1.wells_by_name()[X].top(z=5)) + p50.move_to(sample_plate_1.wells_by_name()[X].top(z=0)) + p50.move_to(sample_plate_1.wells_by_name()[X].top(z=5)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + heatershaker.close_labware_latch() + heatershaker.set_and_wait_for_shake_speed(rpm=heater_shaker_speed) + protocol.delay(EPMMixTime) + heatershaker.deactivate_shaker() + heatershaker.open_labware_latch() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATER SHAKER TO THERMOCYCLER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=thermocycler, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Adding Barcodes") + BarcodeVol = 10 + BarcodeMixRep = 3 + BarcodeMixVol = 10 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p50.pick_up_tip() + p50.aspirate(BarcodeVol + 1, reagent_plate.wells_by_name()[barcodes[loop]].bottom(z=bottom_val), rate=0.25) + p50.dispense(1, reagent_plate.wells_by_name()[barcodes[loop]].bottom(z=bottom_val), rate=0.25) + p50.dispense(BarcodeVol, sample_plate_1.wells_by_name()[X].bottom(z=1)) + p50.mix(BarcodeMixRep, BarcodeMixVol) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + if STEP_PCRDECK == 1: + ############################################################################################################################################ + + protocol.comment("SETTING THERMO to Room Temp") + thermocycler.set_block_temperature(4) + thermocycler.set_lid_temperature(100) + + thermocycler.close_lid() + profile_PCR_1 = [{"temperature": 68, "hold_time_seconds": 180}, {"temperature": 98, "hold_time_seconds": 180}] + thermocycler.execute_profile(steps=profile_PCR_1, repetitions=1, block_max_volume=50) + profile_PCR_2 = [ + {"temperature": 98, "hold_time_seconds": 45}, + {"temperature": 62, "hold_time_seconds": 30}, + {"temperature": 68, "hold_time_seconds": 120}, + ] + thermocycler.execute_profile(steps=profile_PCR_2, repetitions=PCRCYCLES, block_max_volume=50) + profile_PCR_3 = [{"temperature": 68, "hold_time_minutes": 1}] + thermocycler.execute_profile(steps=profile_PCR_3, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(10) + ############################################################################################################################################ + thermocycler.open_lid() + + if STEP_CLEANUP == 1: + protocol.comment("==============================================") + protocol.comment("--> Cleanup") + protocol.comment("==============================================") + + # Setting Labware to Resume at Wash + if STEP_TAG == 0 and STEP_WASH == 0: + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATERSHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + else: + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM THERMOCYCLER To HEATERSHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> TRANSFERRING AND ADDING AMPure (0.8x)") + H20Vol = 40 + AMPureVol = 45 + SampleVol = 45 + AMPureMixRPM = 1800 + AMPureMixTime = 5 * 60 + AMPurePremix = 3 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck() + p50.pick_up_tip() + + protocol.comment("--> Adding H20") + + p50.aspirate(H20Vol + 5, H20.bottom(z=bottom_val), rate=1) # original = () + p50.dispense(5, H20.bottom(z=bottom_val), rate=1) # original = () + p50.dispense(H20Vol, sample_plate_1[column_2_list[loop]].bottom(z=0.75)) + + protocol.comment("--> ADDING AMPure (0.8x)") + p50.move_to(AMPure.bottom(z=0.75)) + p50.mix(3, AMPureVol) + p50.aspirate(AMPureVol + 5, AMPure.bottom(z=0.75), rate=0.25) + p50.dispense(5, AMPure.bottom(z=0.75), rate=0.5) + p50.dispense(AMPureVol, sample_plate_1[column_2_list[loop]].bottom(z=0.75), rate=1) + protocol.delay(seconds=0.2) + p50.blow_out(sample_plate_1[column_2_list[loop]].top(z=-2)) + + protocol.comment("--> Adding SAMPLE") + p50.aspirate(SampleVol + 3, sample_plate_1[column_1_list[loop]].bottom(z=0.75), rate=0.5) + p50.dispense(SampleVol + 3, sample_plate_1[column_2_list[loop]].bottom(z=0.75), rate=1) + p50.aspirate(SampleVol + 3, sample_plate_1[column_2_list[loop]].bottom(z=0.75), rate=0.5) + p50.dispense(SampleVol + 3, sample_plate_1[column_2_list[loop]].bottom(z=0.75), rate=1) + p50.move_to(sample_plate_1[column_2_list[loop]].top(z=-3)) + protocol.delay(seconds=0.2) + p50.blow_out(sample_plate_1[column_2_list[loop]].top(z=-3)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=heater_shaker_speed * 0.9) + protocol.delay(AMPureMixTime) + heatershaker.deactivate_shaker() + + protocol.comment("SETTING THERMO to Room Temp") + thermocycler.set_block_temperature(20) + thermocycler.set_lid_temperature(37) + + # ============================================================================================ + # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_2_list): + tipcheck() + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_1[X].bottom(z=0.75)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].top(z=2)) + p1000.default_speed = 200 + Liquid_trash = DispWasteVol(90) + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + # =============================================== + if ETOH_AirMultiDis == True: + tipcheck() + p1000.pick_up_tip() + for loop, X in enumerate(column_2_list): + p1000.aspirate(ETOHMaxVol, EtOH.bottom(z=1)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(EtOH.top(z=-5)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=-2)) + p1000.dispense(ETOHMaxVol, rate=1) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.move_to(sample_plate_1[X].top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + else: + for loop, X in enumerate(column_2_list): + tipcheck() + p1000.pick_up_tip() + p1000.aspirate(ETOHMaxVol, EtOH.bottom(z=1)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(EtOH.top(z=-5)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=-2)) + p1000.dispense(ETOHMaxVol, rate=1) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.move_to(sample_plate_1[X].top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + # =============================================== + for loop, X in enumerate(column_2_list): + tipcheck() + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_1[X].bottom(z=0.75)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].top(z=2)) + p1000.default_speed = 200 + Liquid_trash = DispWasteVol(150) + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual Wash") + # =============================================== + for loop, X in enumerate(column_2_list): + tipcheck() + p50.pick_up_tip() + p50.move_to(sample_plate_1[X].bottom(1)) + p50.aspirate(20, rate=0.25) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM MAG PLATE TO HEATER SHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Adding RSB") + RSBVol = 32 + RSBMixRPM = 2000 + RSBMixTime = 1 * 60 + # =============================================== + if RSB_AirMultiDis == True: + tipcheck() + p50.pick_up_tip() + for loop, X in enumerate(column_2_list): + p50.aspirate(RSBVol, RSB.bottom(z=1)) + p50.move_to(sample_plate_1.wells_by_name()[X].top(z=-3)) + p50.dispense(RSBVol, rate=2) + p50.blow_out(sample_plate_1.wells_by_name()[X].top(z=-3)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + else: + for loop, X in enumerate(column_2_list): + tipcheck() + p50.pick_up_tip() + p50.aspirate(RSBVol, RSB.bottom(z=1)) + p50.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) + p50.dispense(RSBVol, rate=1) + p50.blow_out(sample_plate_1.wells_by_name()[X].top(z=-3)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + tipcheck() + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=heater_shaker_speed) + protocol.delay(RSBMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATER SHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.delay(minutes=3) + + protocol.comment("--> Transferring Supernatant") + TransferSup = 30 + # =============================================== + for loop, X in enumerate(column_2_list): + tipcheck() + p50.pick_up_tip() + p50.move_to(sample_plate_1[X].bottom(z=bottom_val)) + p50.aspirate(TransferSup + 1, rate=0.25) + p50.dispense(TransferSup + 1, sample_plate_1[column_3_list[loop]].bottom(z=1)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== diff --git a/app-testing/files/protocols/Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment.py b/app-testing/files/protocols/Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment.py deleted file mode 100644 index e4b55c71752..00000000000 --- a/app-testing/files/protocols/Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment.py +++ /dev/null @@ -1,794 +0,0 @@ -from opentrons import protocol_api -from opentrons import types - -metadata = { - "protocolName": "Illumina DNA Enrichment", - "author": "Opentrons ", - "source": "Protocol Library", -} - -requirements = { - "robotType": "OT-3", - "apiLevel": "2.15", -} - -# SCRIPT SETTINGS -DRYRUN = "YES" # YES or NO, DRYRUN = 'YES' will return tips, skip incubation times, shorten mix, for testing purposes -USE_GRIPPER = True - - -# PROTOCOL SETTINGS -SAMPLES = "8x" # 8x -HYBRIDDECK = True -HYBRIDTIME = 1.6 # Hours - -# PROTOCOL BLOCKS -STEP_VOLPOOL = 1 -STEP_CAPTURE = 1 -STEP_WASH = 1 -STEP_PCR = 1 -STEP_PCRDECK = 1 -STEP_POSTPCR = 1 -STEP_CLEANUP = 1 - -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ - - -def run(protocol: protocol_api.ProtocolContext): - global DRYRUN - - protocol.comment("THIS IS A DRY RUN") if DRYRUN == "YES" else protocol.comment("THIS IS A REACTION RUN") - - # DECK SETUP AND LABWARE - # ========== FIRST ROW =========== - heatershaker = protocol.load_module("heaterShakerModuleV1", "1") - sample_plate_2 = heatershaker.load_labware("nest_96_wellplate_2ml_deep") - tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "2") - temp_block = protocol.load_module("temperature module gen2", "3") - reagent_plate = temp_block.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") - # ========== SECOND ROW ========== - MAG_PLATE_SLOT = protocol.load_module("magneticBlockV1", "4") - reservoir = protocol.load_labware("nest_96_wellplate_2ml_deep", "5") - tiprack_200_2 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "6") - # ========== THIRD ROW =========== - thermocycler = protocol.load_module("thermocycler module gen2") - sample_plate_1 = thermocycler.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") - tiprack_20 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "9") - # ========== FOURTH ROW ========== - - # reagent - - AMPure = reservoir["A1"] - SMB = reservoir["A2"] - EEW = reservoir["A3"] - EtOH = reservoir["A4"] - RSB = reservoir["A5"] - Liquid_trash = reservoir["A12"] - - EEW_1 = sample_plate_1.wells_by_name()["A8"] - EEW_2 = sample_plate_1.wells_by_name()["A9"] - EEW_3 = sample_plate_1.wells_by_name()["A10"] - EEW_4 = sample_plate_1.wells_by_name()["A11"] - - NHB2 = reagent_plate.wells_by_name()["A1"] - Panel = reagent_plate.wells_by_name()["A2"] - EHB2 = reagent_plate.wells_by_name()["A3"] - Elute = reagent_plate.wells_by_name()["A4"] - ET2 = reagent_plate.wells_by_name()["A5"] - PPC = reagent_plate.wells_by_name()["A6"] - EPM = reagent_plate.wells_by_name()["A7"] - - # pipette - p1000 = protocol.load_instrument("flex_8channel_1000", "left", tip_racks=[tiprack_200_1, tiprack_200_2]) - p50 = protocol.load_instrument("flex_8channel_50", "right", tip_racks=[tiprack_20]) - - # tip and sample tracking - sample_well = "A3" - - WASHES = [EEW_1, EEW_2, EEW_3, EEW_4] - - def grip_offset(action, item, slot=None): - """Grip offset.""" - from opentrons.types import Point - - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _pick_up_offsets = { - "deck": Point(), - "mag-plate": Point(), - "heater-shaker": Point(z=1.0), - "temp-module": Point(), - "thermo-cycler": Point(), - } - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _drop_offsets = { - "deck": Point(), - "mag-plate": Point(z=0.5), - "heater-shaker": Point(y=-0.5), - "temp-module": Point(), - "thermo-cycler": Point(), - } - # do NOT edit these values - # NOTE: these values will eventually be in our software - # and will not need to be inside a protocol - _hw_offsets = { - "deck": Point(), - "mag-plate": Point(z=2.5), - "heater-shaker-right": Point(z=2.5), - "heater-shaker-left": Point(z=2.5), - "temp-module": Point(z=5.0), - "thermo-cycler": Point(z=2.5), - } - # make sure arguments are correct - action_options = ["pick-up", "drop"] - item_options = list(_hw_offsets.keys()) - item_options.remove("heater-shaker-left") - item_options.remove("heater-shaker-right") - item_options.append("heater-shaker") - if action not in action_options: - raise ValueError(f'"{action}" not recognized, available options: {action_options}') - if item not in item_options: - raise ValueError(f'"{item}" not recognized, available options: {item_options}') - if item == "heater-shaker": - assert slot, 'argument slot= is required when using "heater-shaker"' - if slot in [1, 4, 7, 10]: - side = "left" - elif slot in [3, 6, 9, 12]: - side = "right" - else: - raise ValueError("heater shaker must be on either left or right side") - hw_offset = _hw_offsets[f"{item}-{side}"] - else: - hw_offset = _hw_offsets[item] - if action == "pick-up": - offset = hw_offset + _pick_up_offsets[item] - else: - offset = hw_offset + _drop_offsets[item] - - # convert from Point() to dict() - return {"x": offset.x, "y": offset.y, "z": offset.z} - - ############################################################################################################################################ - ############################################################################################################################################ - ############################################################################################################################################ - # commands - heatershaker.open_labware_latch() - if DRYRUN == "NO": - protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") - thermocycler.set_block_temperature(4) - thermocycler.set_lid_temperature(100) - temp_block.set_temperature(4) - thermocycler.open_lid() - protocol.pause("Ready") - heatershaker.close_labware_latch() - - if STEP_VOLPOOL == 1: - protocol.comment("==============================================") - protocol.comment("--> Quick Vol Pool") - protocol.comment("==============================================") - - if STEP_CAPTURE == 1: - protocol.comment("==============================================") - protocol.comment("--> Capture") - protocol.comment("==============================================") - - protocol.comment("--> Adding NHB2") - NHB2Vol = 50 - p50.pick_up_tip() - p50.aspirate(NHB2Vol, NHB2.bottom()) - p50.dispense(NHB2Vol, sample_plate_1[sample_well].bottom()) - p50.return_tip() - - protocol.comment("--> Adding Panel") - PanelVol = 10 - p50.pick_up_tip() - p50.aspirate(PanelVol, Panel.bottom()) - p50.dispense(PanelVol, sample_plate_1[sample_well].bottom()) - p50.return_tip() - - protocol.comment("--> Adding EHB2") - EHB2Vol = 10 - EHB2MixRep = 10 if DRYRUN == "NO" else 1 - EHB2MixVol = 90 - p1000.pick_up_tip() - p1000.aspirate(EHB2Vol, EHB2.bottom()) - p1000.dispense(EHB2Vol, sample_plate_1[sample_well].bottom()) - p1000.move_to(sample_plate_1[sample_well].bottom()) - p1000.mix(EHB2MixRep, EHB2MixVol) - p1000.return_tip() - - if HYBRIDDECK == True: - protocol.comment("Hybridize on Deck") - ############################################################################################################################################ - thermocycler.close_lid() - if DRYRUN == "NO": - profile_TAGSTOP = [ - {"temperature": 98, "hold_time_minutes": 5}, - {"temperature": 97, "hold_time_minutes": 1}, - {"temperature": 95, "hold_time_minutes": 1}, - {"temperature": 93, "hold_time_minutes": 1}, - {"temperature": 91, "hold_time_minutes": 1}, - {"temperature": 89, "hold_time_minutes": 1}, - {"temperature": 87, "hold_time_minutes": 1}, - {"temperature": 85, "hold_time_minutes": 1}, - {"temperature": 83, "hold_time_minutes": 1}, - {"temperature": 81, "hold_time_minutes": 1}, - {"temperature": 79, "hold_time_minutes": 1}, - {"temperature": 77, "hold_time_minutes": 1}, - {"temperature": 75, "hold_time_minutes": 1}, - {"temperature": 73, "hold_time_minutes": 1}, - {"temperature": 71, "hold_time_minutes": 1}, - {"temperature": 69, "hold_time_minutes": 1}, - {"temperature": 67, "hold_time_minutes": 1}, - {"temperature": 65, "hold_time_minutes": 1}, - {"temperature": 63, "hold_time_minutes": 1}, - {"temperature": 62, "hold_time_minutes": HYBRIDTIME * 60}, - ] - thermocycler.execute_profile(steps=profile_TAGSTOP, repetitions=1, block_max_volume=100) - thermocycler.set_block_temperature(10) - thermocycler.open_lid() - ############################################################################################################################################ - else: - protocol.comment("Hybridize off Deck") - - if STEP_CAPTURE == 1: - if DRYRUN == "NO": - heatershaker.set_and_wait_for_temperature(62) - - protocol.comment("--> Heating EEW") - EEWVol = 120 - p1000.pick_up_tip() - for loop, X in enumerate(["A8", "A9", "A10", "A11"]): - p1000.aspirate(EEWVol + 1, EEW.bottom(z=0.25), rate=0.25) - p1000.dispense(EEWVol + 5, sample_plate_1[sample_well].bottom(z=1)) - p1000.return_tip() # <---------------- Tip Return - - protocol.comment("--> Transfer Hybridization") - TransferSup = 100 - p1000.pick_up_tip() - p1000.move_to(sample_plate_1[sample_well].bottom(z=0.25)) - p1000.aspirate(TransferSup + 1, rate=0.25) - p1000.dispense(TransferSup + 5, sample_plate_2[sample_well].bottom(z=1)) - p1000.return_tip() - - thermocycler.close_lid() - - protocol.comment("--> ADDING SMB") - SMBVol = 250 - SampleVol = 100 - SMBMixRep = 15 * 60 if DRYRUN == "NO" else 0.1 * 60 - SMBPremix = 3 if DRYRUN == "NO" else 1 - # ========NEW SINGLE TIP DISPENSE=========== - p1000.pick_up_tip() - p1000.mix(SMBMixRep, 200, SMB.bottom(z=1)) - p1000.aspirate(SMBVol / 2, SMB.bottom(z=1), rate=0.25) - p1000.dispense(SMBVol / 2, sample_plate_2[sample_well].top(z=2), rate=0.25) - p1000.aspirate(SMBVol / 2, SMB.bottom(z=1), rate=0.25) - p1000.dispense(SMBVol / 2, sample_plate_2[sample_well].bottom(z=1), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(sample_plate_2[sample_well].bottom(z=5)) - for Mix in range(2): - p1000.aspirate(100, rate=0.5) - p1000.move_to(sample_plate_2[sample_well].bottom(z=1)) - p1000.aspirate(80, rate=0.5) - p1000.dispense(80, rate=0.5) - p1000.move_to(sample_plate_2[sample_well].bottom(z=5)) - p1000.dispense(100, rate=0.5) - Mix += 1 - p1000.blow_out(sample_plate_2[sample_well].top(z=2)) - p1000.default_speed = 400 - p1000.move_to(sample_plate_2[sample_well].top(z=5)) - p1000.move_to(sample_plate_2[sample_well].top(z=0)) - p1000.move_to(sample_plate_2[sample_well].top(z=5)) - p1000.return_tip() - # ========NEW HS MIX========================= - protocol.delay(SMBMixRep) - - # ============================================================================================ - # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - thermocycler.open_lid() - - if DRYRUN == "NO": - protocol.delay(minutes=2) - - protocol.comment("==============================================") - protocol.comment("--> WASH") - protocol.comment("==============================================") - - protocol.comment("--> Remove SUPERNATANT") - p1000.pick_up_tip() - p1000.move_to(sample_plate_2[sample_well].bottom(4)) - p1000.aspirate(200, rate=0.25) - p1000.dispense(200, Liquid_trash) - p1000.aspirate(200, rate=0.25) - p1000.dispense(200, Liquid_trash) - p1000.move_to(Liquid_trash.top(z=5)) - protocol.delay(minutes=0.1) - p1000.blow_out(Liquid_trash.top(z=5)) - p1000.aspirate(20) - p1000.return_tip() - - # ============================================================================================ - # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Repeating 3 washes") - washreps = 3 - for wash in range(washreps): - protocol.comment("--> Adding EEW") - EEWVol = 200 - p1000.pick_up_tip() - p1000.aspirate(EEWVol, WASHES[wash].bottom()) - p1000.dispense(EEWVol, sample_plate_2[sample_well].bottom()) - p1000.return_tip() - - heatershaker.close_labware_latch() - heatershaker.set_and_wait_for_shake_speed(rpm=1600) - protocol.delay(seconds=4 * 60) - heatershaker.deactivate_shaker() - heatershaker.open_labware_latch() - - if DRYRUN == "NO": - protocol.delay(seconds=5 * 60) - - # ============================================================================================ - # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Removing Supernatant") - RemoveSup = 200 - p1000.pick_up_tip() - p1000.move_to(sample_plate_2[sample_well].bottom(z=3.5)) - p1000.aspirate(RemoveSup - 100, rate=0.25) - protocol.delay(minutes=0.1) - p1000.move_to(sample_plate_2[sample_well].bottom(z=0.5)) - p1000.aspirate(100, rate=0.25) - p1000.move_to(sample_plate_2[sample_well].top(z=2)) - p1000.dispense(200, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p1000.blow_out(Liquid_trash.top(z=0)) - p1000.aspirate(20) - p1000.return_tip() - - # ============================================================================================ - # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Adding EEW") - EEWVol = 200 - p1000.pick_up_tip() - p1000.aspirate(EEWVol, WASHES[3].bottom()) - p1000.dispense(EEWVol, sample_plate_2[sample_well].bottom()) - p1000.return_tip() - - heatershaker.set_and_wait_for_shake_speed(rpm=1600) - if DRYRUN == "NO": - protocol.delay(seconds=4 * 60) - heatershaker.deactivate_shaker() - - protocol.comment("--> Transfer Hybridization") - TransferSup = 200 - p1000.pick_up_tip() - p1000.move_to(sample_plate_2[sample_well].bottom(z=0.25)) - p1000.aspirate(TransferSup, rate=0.25) - sample_well = "A4" - p1000.dispense(TransferSup, sample_plate_2[sample_well].bottom(z=1)) - p1000.return_tip() - - protocol.delay(seconds=5 * 60) - - # ============================================================================================ - # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Removing Supernatant") - RemoveSup = 200 - p1000.pick_up_tip() - p1000.move_to(sample_plate_2[sample_well].bottom(z=3.5)) - p1000.aspirate(RemoveSup - 100, rate=0.25) - protocol.delay(minutes=0.1) - p1000.move_to(sample_plate_2[sample_well].bottom(z=0.5)) - p1000.aspirate(100, rate=0.25) - p1000.move_to(sample_plate_2[sample_well].top(z=2)) - p1000.dispense(200, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p1000.blow_out(Liquid_trash.top(z=0)) - p1000.aspirate(20) - p1000.return_tip() - - protocol.comment("--> Removing Residual") - p50.pick_up_tip() - p50.move_to(sample_plate_2[sample_well].bottom(z=0)) - p50.aspirate(50, rate=0.25) - p50.default_speed = 200 - p50.dispense(100, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p50.blow_out() - p50.default_speed = 400 - p50.move_to(Liquid_trash.top(z=-5)) - p50.move_to(Liquid_trash.top(z=0)) - p50.return_tip() - - protocol.comment("==============================================") - protocol.comment("--> ELUTE") - protocol.comment("==============================================") - - protocol.comment("--> Adding EE1") - EluteVol = 23 - p50.pick_up_tip() - p50.aspirate(EluteVol, Elute.bottom()) - p50.dispense(EluteVol, sample_plate_2[sample_well].bottom()) - p50.return_tip() - - # ============================================================================================ - # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - heatershaker.close_labware_latch() - heatershaker.set_and_wait_for_shake_speed(rpm=1600) - if DRYRUN == "NO": - protocol.delay(seconds=2 * 60) - heatershaker.deactivate_shaker() - heatershaker.open_labware_latch() - - if DRYRUN == "NO": - protocol.delay(minutes=2) - - # ============================================================================================ - # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Transfer Elution") - TransferSup = 21 - p50.pick_up_tip() - p50.move_to(sample_plate_2[sample_well].bottom(z=0.25)) - p50.aspirate(TransferSup + 1, rate=0.25) - sample_well = "A5" - p50.dispense(TransferSup + 5, sample_plate_1[sample_well].bottom(z=1)) - p50.return_tip() - - protocol.comment("--> Adding ET2") - ET2Vol = 4 - ET2MixRep = 10 if DRYRUN == "NO" else 1 - ET2MixVol = 20 - p50.pick_up_tip() - p50.aspirate(ET2Vol, ET2.bottom()) - p50.dispense(ET2Vol, sample_plate_1[sample_well].bottom()) - p50.move_to(sample_plate_1[X].bottom()) - p50.mix(ET2MixRep, ET2MixVol) - p50.return_tip() - - if STEP_PCR == 1: - protocol.comment("==============================================") - protocol.comment("--> AMPLIFICATION") - protocol.comment("==============================================") - - protocol.comment("--> Adding PPC") - PPCVol = 5 - p50.pick_up_tip() - p50.aspirate(PPCVol, PPC.bottom()) - p50.dispense(PPCVol, sample_plate_1[sample_well].bottom()) - p50.return_tip() - - protocol.comment("--> Adding EPM") - EPMVol = 20 - EPMMixRep = 10 if DRYRUN == "NO" else 1 - EPMMixVol = 45 - p50.pick_up_tip() - p50.aspirate(EPMVol, EPM.bottom()) - p50.dispense(EPMVol, sample_plate_1[sample_well].bottom()) - p50.move_to(sample_plate_1[sample_well].bottom()) - p50.mix(EPMMixRep, EPMMixVol) - p50.return_tip() - - heatershaker.deactivate_heater() - - if STEP_PCRDECK == 1: - if DRYRUN == "NO": - ############################################################################################################################################ - protocol.pause("Seal, Run PCR (60min)") - if DRYRUN == "NO": - thermocycler.close_lid() - profile_PCR_1 = [{"temperature": 98, "hold_time_seconds": 45}] - thermocycler.execute_profile(steps=profile_PCR_1, repetitions=1, block_max_volume=50) - profile_PCR_2 = [ - {"temperature": 98, "hold_time_seconds": 30}, - {"temperature": 60, "hold_time_seconds": 30}, - {"temperature": 72, "hold_time_seconds": 30}, - ] - thermocycler.execute_profile(steps=profile_PCR_2, repetitions=12, block_max_volume=50) - profile_PCR_3 = [{"temperature": 72, "hold_time_minutes": 1}] - thermocycler.execute_profile(steps=profile_PCR_3, repetitions=1, block_max_volume=50) - thermocycler.set_block_temperature(10) - ############################################################################################################################################ - thermocycler.open_lid() - - if STEP_CLEANUP == 1: - protocol.comment("==============================================") - protocol.comment("--> Cleanup") - protocol.comment("==============================================") - - # ============================================================================================ - # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Transfer Elution") - TransferSup = 45 - p50.pick_up_tip() - p50.move_to(sample_plate_1[sample_well].bottom(z=0.25)) - p50.aspirate(TransferSup + 1, rate=0.25) - sample_well = "A5" - p50.dispense(TransferSup + 5, sample_plate_2[sample_well].bottom(z=1)) - p50.return_tip() - - protocol.comment("--> ADDING AMPure (0.8x)") - AMPureVol = 40.5 - SampleVol = 45 - AMPureMixRep = 5 * 60 if DRYRUN == "NO" else 0.1 * 60 - AMPurePremix = 3 if DRYRUN == "NO" else 1 - # ========NEW SINGLE TIP DISPENSE=========== - p1000.pick_up_tip() - p1000.mix(AMPurePremix, AMPureVol + 10, AMPure.bottom(z=1)) - p1000.aspirate(AMPureVol, AMPure.bottom(z=1), rate=0.25) - p1000.dispense(AMPureVol, sample_plate_2[sample_well].bottom(z=1), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(sample_plate_2[sample_well].bottom(z=5)) - for Mix in range(2): - p1000.aspirate(60, rate=0.5) - p1000.move_to(sample_plate_2[sample_well].bottom(z=1)) - p1000.aspirate(60, rate=0.5) - p1000.dispense(60, rate=0.5) - p1000.move_to(sample_plate_2[sample_well].bottom(z=5)) - p1000.dispense(30, rate=0.5) - Mix += 1 - p1000.blow_out(sample_plate_2[sample_well].top(z=2)) - p1000.default_speed = 400 - p1000.move_to(sample_plate_2[sample_well].top(z=5)) - p1000.move_to(sample_plate_2[sample_well].top(z=0)) - p1000.move_to(sample_plate_2[sample_well].top(z=5)) - p1000.return_tip() - # ========NEW HS MIX========================= - heatershaker.set_and_wait_for_shake_speed(rpm=1800) - protocol.delay(AMPureMixRep) - heatershaker.deactivate_shaker() - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - if DRYRUN == "NO": - protocol.delay(minutes=4) - - protocol.comment("--> Removing Supernatant") - RemoveSup = 200 - p1000.pick_up_tip() - p1000.move_to(sample_plate_2[sample_well].bottom(z=3.5)) - p1000.aspirate(RemoveSup - 100, rate=0.25) - protocol.delay(minutes=0.1) - p1000.move_to(sample_plate_2[sample_well].bottom(z=0.5)) - p1000.aspirate(100, rate=0.25) - p1000.default_speed = 5 - p1000.move_to(sample_plate_2[sample_well].top(z=2)) - p1000.default_speed = 200 - p1000.dispense(200, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p1000.blow_out() - p1000.default_speed = 400 - p1000.move_to(Liquid_trash.top(z=-5)) - p1000.move_to(Liquid_trash.top(z=0)) - p1000.return_tip() - - for X in range(2): - protocol.comment("--> ETOH Wash") - ETOHMaxVol = 150 - p1000.pick_up_tip() - p1000.aspirate(ETOHMaxVol, EtOH.bottom(z=1)) - p1000.move_to(EtOH.top(z=0)) - p1000.move_to(EtOH.top(z=-5)) - p1000.move_to(EtOH.top(z=0)) - p1000.move_to(sample_plate_2[sample_well].top(z=-2)) - p1000.dispense(ETOHMaxVol, rate=1) - protocol.delay(minutes=0.1) - p1000.blow_out() - p1000.move_to(sample_plate_2[sample_well].top(z=5)) - p1000.move_to(sample_plate_2[sample_well].top(z=0)) - p1000.move_to(sample_plate_2[sample_well].top(z=5)) - p1000.return_tip() - - if DRYRUN == "NO": - protocol.delay(minutes=0.5) - - protocol.comment("--> Remove ETOH Wash") - p1000.pick_up_tip() - p1000.move_to(sample_plate_2[sample_well].bottom(z=3.5)) - p1000.aspirate(RemoveSup - 100, rate=0.25) - protocol.delay(minutes=0.1) - p1000.move_to(sample_plate_2[sample_well].bottom(z=0.5)) - p1000.aspirate(100, rate=0.25) - p1000.default_speed = 5 - p1000.move_to(sample_plate_2[sample_well].top(z=2)) - p1000.default_speed = 200 - p1000.dispense(200, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p1000.blow_out() - p1000.default_speed = 400 - p1000.move_to(Liquid_trash.top(z=-5)) - p1000.move_to(Liquid_trash.top(z=0)) - p1000.return_tip() - - if DRYRUN == "NO": - protocol.delay(minutes=2) - - protocol.comment("--> Removing Residual ETOH") - p1000.pick_up_tip() - p1000.move_to(sample_plate_2[sample_well].bottom(z=0)) - p1000.aspirate(50, rate=0.25) - p1000.default_speed = 200 - p1000.dispense(100, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p1000.blow_out() - p1000.default_speed = 400 - p1000.move_to(Liquid_trash.top(z=-5)) - p1000.move_to(Liquid_trash.top(z=0)) - p1000.return_tip() - - if DRYRUN == "NO": - protocol.delay(minutes=1) - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM MAG PLATE TO HEATER SHAKER - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Adding RSB") - RSBVol = 32 - RSBMixRep = 1 * 60 if DRYRUN == "NO" else 0.1 * 60 - p1000.pick_up_tip() - p1000.aspirate(RSBVol, RSB.bottom(z=1)) - - p1000.move_to((sample_plate_2.wells_by_name()[sample_well].center().move(types.Point(x=1.3 * 0.8, y=0, z=-4)))) - p1000.dispense(RSBVol, rate=1) - p1000.move_to(sample_plate_2.wells_by_name()[sample_well].bottom(z=1)) - p1000.aspirate(RSBVol, rate=1) - p1000.move_to((sample_plate_2.wells_by_name()[sample_well].center().move(types.Point(x=0, y=1.3 * 0.8, z=-4)))) - p1000.dispense(RSBVol, rate=1) - p1000.move_to(sample_plate_2.wells_by_name()[sample_well].bottom(z=1)) - p1000.aspirate(RSBVol, rate=1) - p1000.move_to((sample_plate_2.wells_by_name()[sample_well].center().move(types.Point(x=1.3 * -0.8, y=0, z=-4)))) - p1000.dispense(RSBVol, rate=1) - p1000.move_to(sample_plate_2.wells_by_name()[sample_well].bottom(z=1)) - p1000.aspirate(RSBVol, rate=1) - p1000.move_to((sample_plate_2.wells_by_name()[sample_well].center().move(types.Point(x=0, y=1.3 * -0.8, z=-4)))) - p1000.dispense(RSBVol, rate=1) - p1000.move_to(sample_plate_2.wells_by_name()[sample_well].bottom(z=1)) - p1000.aspirate(RSBVol, rate=1) - p1000.dispense(RSBVol, rate=1) - - p1000.blow_out(sample_plate_2.wells_by_name()[sample_well].center()) - p1000.move_to(sample_plate_2.wells_by_name()[sample_well].top(z=5)) - p1000.move_to(sample_plate_2.wells_by_name()[sample_well].top(z=0)) - p1000.move_to(sample_plate_2.wells_by_name()[sample_well].top(z=5)) - p1000.return_tip() - heatershaker.set_and_wait_for_shake_speed(rpm=1600) - protocol.delay(RSBMixRep) - heatershaker.deactivate_shaker() - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_2, - new_location=MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - if DRYRUN == "NO": - protocol.delay(minutes=3) - - protocol.comment("--> Transferring Supernatant") - TransferSup = 30 - p1000.pick_up_tip() - p1000.move_to(sample_plate_2[sample_well].bottom(z=0.25)) - p1000.aspirate(TransferSup + 1, rate=0.25) - p1000.dispense(TransferSup + 5, sample_plate_2["A7"].bottom(z=1)) - p1000.return_tip() diff --git a/app-testing/files/protocols/Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x.py b/app-testing/files/protocols/Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x.py deleted file mode 100644 index 4d35cdf23fe..00000000000 --- a/app-testing/files/protocols/Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x.py +++ /dev/null @@ -1,691 +0,0 @@ -from opentrons import protocol_api -from opentrons import types - -metadata = { - "protocolName": "Illumina DNA Prep 24x", - "author": "Opentrons ", - "source": "Protocol Library", -} - -requirements = { - "robotType": "OT-3", - "apiLevel": "2.15", -} - -# SCRIPT SETTINGS -DRYRUN = "NO" # YES or NO, DRYRUN = 'YES' will return tips, skip incubation times, shorten mix, for testing purposes -USE_GRIPPER = True - -# PROTOCOL SETTINGS -COLUMNS = 3 # 1-3 - -# PROTOCOL BLOCKS -STEP_TAG = 1 -STEP_WASH = 1 -STEP_PCRDECK = 1 -STEP_CLEANUP = 1 - -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ - - -def run(protocol: protocol_api.ProtocolContext): - global DRYRUN - - protocol.comment("THIS IS A DRY RUN") if DRYRUN == "YES" else protocol.comment("THIS IS A REACTION RUN") - - # DECK SETUP AND LABWARE - # ========== FIRST ROW =========== - heatershaker = protocol.load_module("heaterShakerModuleV1", "1") - sample_plate_1 = heatershaker.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") - tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "2") - temp_block = protocol.load_module("temperature module gen2", "3") - reagent_plate = temp_block.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") - # ========== SECOND ROW ========== - mag_block = protocol.load_module("magneticBlockV1", 4) - reservoir = protocol.load_labware("nest_96_wellplate_2ml_deep", "5") - tiprack_200_2 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "6") - # ========== THIRD ROW =========== - thermocycler = protocol.load_module("thermocycler module gen2") - tiprack_20 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "9") - # ========== FOURTH ROW ========== - - # =========== RESERVOIR ========== - AMPure = reservoir["A1"] - WASH_1 = reservoir["A2"] - WASH_2 = reservoir["A3"] - WASH_3 = reservoir["A4"] - EtOH = reservoir["A8"] - RSB = reservoir["A9"] - H20 = reservoir["A10"] - Liquid_trash = reservoir["A11"] - # ========= REAGENT PLATE ========= - TAGMIX = reagent_plate["A1"] - TAGSTOP = reagent_plate["A2"] - EPM = reagent_plate["A3"] - Barcodes1 = reagent_plate.wells_by_name()["A7"] - Barcodes2 = reagent_plate.wells_by_name()["A8"] - Barcodes3 = reagent_plate.wells_by_name()["A9"] - - # pipette - p1000 = protocol.load_instrument("flex_8channel_1000", "right", tip_racks=[tiprack_200_1, tiprack_200_2]) - p50 = protocol.load_instrument("flex_8channel_50", "left", tip_racks=[tiprack_20]) - - # tip and sample tracking - if COLUMNS == 3: - column_1_list = ["A1", "A2", "A3"] - column_2_list = ["A5", "A6", "A7"] - column_3_list = ["A9", "A10", "A11"] - barcodes = ["A7", "A8", "A9"] - - TIP_SUP = [tiprack_200_1["A1"], tiprack_200_1["A2"], tiprack_200_1["A3"]] - TIP_WASH = [tiprack_200_1["A4"], tiprack_200_1["A5"], tiprack_200_1["A6"]] - WASH = [WASH_1, WASH_2, WASH_3] - TIP_EPM = [tiprack_200_1["A7"], tiprack_200_1["A8"], tiprack_200_1["A9"]] - TIP_TRANSFER = [tiprack_200_1["A10"], tiprack_200_1["A11"], tiprack_200_1["A12"]] - TIP_CLEANUP = [tiprack_200_2["A1"], tiprack_200_2["A2"], tiprack_200_2["A3"]] - TIP_RSB = [tiprack_200_2["A4"], tiprack_200_2["A5"], tiprack_200_2["A6"]] - Tip_ETOH = tiprack_200_2["A7"] - - def grip_offset(action, item, slot=None): - """Grip offset.""" - from opentrons.types import Point - - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _pick_up_offsets = { - "deck": Point(), - "mag-plate": Point(), - "heater-shaker": Point(z=1.0), - "temp-module": Point(), - "thermo-cycler": Point(), - } - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _drop_offsets = { - "deck": Point(), - "mag-plate": Point(z=0.5), - "heater-shaker": Point(), - "temp-module": Point(), - "thermo-cycler": Point(), - } - # do NOT edit these values - # NOTE: these values will eventually be in our software - # and will not need to be inside a protocol - _hw_offsets = { - "deck": Point(), - "mag-plate": Point(z=2.5), - "heater-shaker-right": Point(z=2.5), - "heater-shaker-left": Point(z=2.5), - "temp-module": Point(z=5.0), - "thermo-cycler": Point(z=4.5), - } - # make sure arguments are correct - action_options = ["pick-up", "drop"] - item_options = list(_hw_offsets.keys()) - item_options.remove("heater-shaker-left") - item_options.remove("heater-shaker-right") - item_options.append("heater-shaker") - if action not in action_options: - raise ValueError(f'"{action}" not recognized, available options: {action_options}') - if item not in item_options: - raise ValueError(f'"{item}" not recognized, available options: {item_options}') - if item == "heater-shaker": - assert slot, 'argument slot= is required when using "heater-shaker"' - if slot in [1, 4, 7, 10]: - side = "left" - elif slot in [3, 6, 9, 12]: - side = "right" - else: - raise ValueError("heater shaker must be on either left or right side") - hw_offset = _hw_offsets[f"{item}-{side}"] - else: - hw_offset = _hw_offsets[item] - if action == "pick-up": - offset = hw_offset + _pick_up_offsets[item] - else: - offset = hw_offset + _drop_offsets[item] - - # convert from Point() to dict() - return {"x": offset.x, "y": offset.y, "z": offset.z} - - ############################################################################################################################################ - ############################################################################################################################################ - ############################################################################################################################################ - # commands - heatershaker.open_labware_latch() - if DRYRUN == "NO": - protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") - thermocycler.set_block_temperature(4) - thermocycler.set_lid_temperature(100) - temp_block.set_temperature(4) - thermocycler.open_lid() - protocol.pause("Ready") - heatershaker.close_labware_latch() - - if STEP_TAG == 1: - protocol.comment("==============================================") - protocol.comment("--> Tagment") - protocol.comment("==============================================") - - protocol.comment("--> ADDING TAGMIX") - TagVol = 20 - SampleVol = 60 - TagMixRep = 5 * 60 if DRYRUN == "NO" else 0.1 * 60 - TagPremix = 3 if DRYRUN == "NO" else 1 - # ========NEW SINGLE TIP DISPENSE=========== - for loop, X in enumerate(column_1_list): - p1000.pick_up_tip(TIP_SUP[loop]) # <---------------- Tip Pickup - p1000.mix(TagPremix, TagVol + 10, TAGMIX.bottom(z=1)) - p1000.aspirate(TagVol, TAGMIX.bottom(z=1), rate=0.25) - p1000.dispense(TagVol, sample_plate_1[X].bottom(z=1), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(sample_plate_1[X].bottom(z=3.5)) - for Mix in range(2): - p1000.aspirate(40, rate=0.5) - p1000.move_to(sample_plate_1[X].bottom(z=1)) - p1000.aspirate(20, rate=0.5) - p1000.dispense(20, rate=0.5) - p1000.move_to(sample_plate_1[X].bottom(z=3.5)) - p1000.dispense(40, rate=0.5) - Mix += 1 - p1000.blow_out(sample_plate_1[X].top(z=2)) - p1000.default_speed = 400 - p1000.move_to(sample_plate_1[X].top(z=5)) - p1000.move_to(sample_plate_1[X].top(z=0)) - p1000.move_to(sample_plate_1[X].top(z=5)) - p1000.return_tip() # <---------------- Tip Return - # ========NEW HS MIX========================= - heatershaker.set_and_wait_for_shake_speed(rpm=1800) - protocol.delay(TagMixRep) - heatershaker.deactivate_shaker() - - # ============================================================================================ - # GRIPPER MOVE sample_plate_1 FROM HEATERSHAKER TO THERMOCYCLER - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_1, - new_location=thermocycler, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "thermo-cycler"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - ############################################################################################################################################ - thermocycler.close_lid() - if DRYRUN == "NO": - profile_TAG = [{"temperature": 55, "hold_time_minutes": 15}] - thermocycler.execute_profile(steps=profile_TAG, repetitions=1, block_max_volume=50) - thermocycler.set_block_temperature(10) - thermocycler.open_lid() - ############################################################################################################################################ - - protocol.comment("--> Adding TAGSTOP") - TAGSTOPVol = 10 - TAGSTOPMixRep = 10 if DRYRUN == "NO" else 1 - TAGSTOPMixVol = 20 - for loop, X in enumerate(column_1_list): - p50.pick_up_tip() - p50.aspirate(TAGSTOPVol, TAGSTOP.bottom()) - p50.dispense(TAGSTOPVol, sample_plate_1[X].bottom()) - p50.move_to(sample_plate_1[X].bottom()) - p50.mix(TAGSTOPMixRep, TAGSTOPMixVol) - p50.return_tip() - - ############################################################################################################################################ - thermocycler.close_lid() - if DRYRUN == "NO": - profile_TAGSTOP = [{"temperature": 37, "hold_time_minutes": 15}] - thermocycler.execute_profile(steps=profile_TAGSTOP, repetitions=1, block_max_volume=50) - thermocycler.set_block_temperature(10) - thermocycler.open_lid() - ############################################################################################################################################ - - # ============================================================================================ - # GRIPPER MOVE sample_plate_1 FROM THERMOCYCLER TO MAG PLATE - protocol.move_labware( - labware=sample_plate_1, - new_location=mag_block, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "thermo-cycler"), - drop_offset=grip_offset("drop", "mag-plate"), - ) - # ============================================================================================ - - if DRYRUN == "NO": - protocol.delay(minutes=4) - - if STEP_WASH == 1: - protocol.comment("==============================================") - protocol.comment("--> Cleanup") - protocol.comment("==============================================") - - protocol.comment("--> Removing Supernatant") - RemoveSup = 200 - for loop, X in enumerate(column_1_list): - p1000.pick_up_tip(TIP_SUP[loop]) # <---------------- Tip Pickup - p1000.move_to(sample_plate_1[X].bottom(z=3.5)) - p1000.aspirate(RemoveSup - 100, rate=0.25) - protocol.delay(minutes=0.1) - p1000.move_to(sample_plate_1[X].bottom(z=0.5)) - p1000.aspirate(100, rate=0.25) - p1000.move_to(sample_plate_1[X].top(z=2)) - p1000.dispense(200, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p1000.blow_out(Liquid_trash.top(z=0)) - p1000.aspirate(20) - p1000.return_tip() # <---------------- Tip Return - - for X in range(3): - # ============================================================================================ - # GRIPPER MOVE PLATE FROM MAG PLATE TO HEATER SHAKER - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_1, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Wash ") - WASHMaxVol = 100 - WASHTime = 3 * 60 if DRYRUN == "NO" else 0.1 * 60 - for loop, X in enumerate(column_1_list): - p1000.pick_up_tip(TIP_WASH[loop]) # <---------------- Tip Pickup - p1000.aspirate(WASHMaxVol, WASH[loop].bottom(z=1), rate=0.25) - p1000.move_to(sample_plate_1[X].bottom(z=1)) - p1000.dispense(WASHMaxVol, rate=0.25) - p1000.mix(2, 90, rate=0.5) - p1000.move_to(sample_plate_1[X].top(z=1)) - protocol.delay(minutes=0.1) - p1000.blow_out(sample_plate_1[X].top(z=1)) - p1000.aspirate(20) - p1000.return_tip() # <---------------- Tip Return - - heatershaker.close_labware_latch() - heatershaker.set_and_wait_for_shake_speed(rpm=1600) - protocol.delay(WASHTime) - heatershaker.deactivate_shaker() - heatershaker.open_labware_latch() - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_1, - new_location=mag_block, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - if DRYRUN == "NO": - protocol.delay(minutes=3) - - protocol.comment("--> Remove Wash") - for loop, X in enumerate(column_1_list): - p1000.pick_up_tip(TIP_WASH[loop]) # <---------------- Tip Pickup - p1000.move_to(sample_plate_1[X].bottom(4)) - p1000.aspirate(WASHMaxVol, rate=0.25) - p1000.default_speed = 5 - p1000.move_to(sample_plate_1[X].bottom()) - protocol.delay(minutes=0.1) - p1000.aspirate(200 - WASHMaxVol, rate=0.25) - p1000.default_speed = 400 - p1000.dispense(200, Liquid_trash) - p1000.move_to(Liquid_trash.top(z=5)) - protocol.delay(minutes=0.1) - p1000.blow_out(Liquid_trash.top(z=5)) - p1000.aspirate(20) - p1000.return_tip() # <---------------- Tip Return - - if DRYRUN == "NO": - protocol.delay(minutes=1) - - protocol.comment("--> Removing Residual Wash") - for loop, X in enumerate(column_1_list): - p1000.pick_up_tip(TIP_WASH[loop]) # <---------------- Tip Pickup - p1000.move_to(sample_plate_1[X].bottom(1)) - p1000.aspirate(20, rate=0.25) - p1000.return_tip() # <---------------- Tip Return - - if DRYRUN == "NO": - protocol.delay(minutes=0.5) - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM MAG PLATE TO HEATER SHAKER - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_1, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Adding EPM") - EPMVol = 40 - EPMMixRep = 5 if DRYRUN == "NO" else 1 - EPMMixVol = 35 - EPMVolCount = 0 - for loop, X in enumerate(column_1_list): - p1000.pick_up_tip(TIP_EPM[loop]) # <---------------- Tip Pickup - p1000.aspirate(EPMVol, EPM.bottom(z=1)) - EPMVolCount += 1 - p1000.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=1.3 * 0.8, y=0, z=-4)))) - p1000.dispense(EPMMixVol, rate=1) - p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) - p1000.aspirate(EPMMixVol, rate=1) - p1000.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=0, y=1.3 * 0.8, z=-4)))) - p1000.dispense(EPMMixVol, rate=1) - p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) - p1000.aspirate(EPMMixVol, rate=1) - p1000.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=1.3 * -0.8, y=0, z=-4)))) - p1000.dispense(EPMMixVol, rate=1) - p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) - p1000.aspirate(EPMMixVol, rate=1) - p1000.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=0, y=1.3 * -0.8, z=-4)))) - p1000.dispense(EPMMixVol, rate=1) - p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) - p1000.aspirate(EPMMixVol, rate=1) - p1000.dispense(EPMMixVol, rate=1) - - p1000.blow_out(sample_plate_1.wells_by_name()[X].center()) - p1000.move_to(sample_plate_1.wells_by_name()[X].top(z=5)) - p1000.move_to(sample_plate_1.wells_by_name()[X].top(z=0)) - p1000.move_to(sample_plate_1.wells_by_name()[X].top(z=5)) - p1000.return_tip() # <---------------- Tip Return - heatershaker.close_labware_latch() - heatershaker.set_and_wait_for_shake_speed(rpm=2000) - protocol.delay(minutes=3) - heatershaker.deactivate_shaker() - heatershaker.open_labware_latch() - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM HEATER SHAKER TO THERMOCYCLER - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_1, - new_location=thermocycler, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "thermo-cycler"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Adding Barcodes") - BarcodeVol = 10 - BarcodeMixRep = 3 if DRYRUN == "NO" else 1 - BarcodeMixVol = 10 - for loop, X in enumerate(column_1_list): - p50.pick_up_tip() - p50.aspirate(BarcodeVol, reagent_plate.wells_by_name()[barcodes[loop]].bottom(), rate=0.25) - p50.dispense(BarcodeVol, sample_plate_1.wells_by_name()[X].bottom(1)) - p50.mix(BarcodeMixRep, BarcodeMixVol) - p50.return_tip() - - if STEP_PCRDECK == 1: - ############################################################################################################################################ - - if DRYRUN == "NO": - thermocycler.set_lid_temperature(100) - thermocycler.close_lid() - if DRYRUN == "NO": - profile_PCR_1 = [ - {"temperature": 68, "hold_time_seconds": 180}, - {"temperature": 98, "hold_time_seconds": 180}, - ] - thermocycler.execute_profile(steps=profile_PCR_1, repetitions=1, block_max_volume=50) - profile_PCR_2 = [ - {"temperature": 98, "hold_time_seconds": 45}, - {"temperature": 62, "hold_time_seconds": 30}, - {"temperature": 68, "hold_time_seconds": 120}, - ] - thermocycler.execute_profile(steps=profile_PCR_2, repetitions=5, block_max_volume=50) - profile_PCR_3 = [{"temperature": 68, "hold_time_minutes": 1}] - thermocycler.execute_profile(steps=profile_PCR_3, repetitions=1, block_max_volume=50) - thermocycler.set_block_temperature(10) - ############################################################################################################################################ - thermocycler.open_lid() - - if STEP_CLEANUP == 1: - protocol.comment("==============================================") - protocol.comment("--> Cleanup") - protocol.comment("==============================================") - - # ============================================================================================ - # GRIPPER MOVE sample_plate_1 FROM THERMOCYCLER To HEATERSHAKER - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_1, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "thermo-cycler"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Adding H20 and SAMPLE") - H20Vol = 40 - SampleVol = 45 - for loop, X in enumerate(column_1_list): - p1000.pick_up_tip(TIP_TRANSFER[loop]) # <---------------- Tip Pickup - p1000.aspirate(H20Vol, H20.bottom(), rate=1) - p1000.dispense(H20Vol, sample_plate_1[column_2_list[loop]].bottom(1)) - p1000.aspirate(SampleVol, sample_plate_1[column_1_list[loop]].bottom(), rate=1) - p1000.dispense(SampleVol, sample_plate_1[column_2_list[loop]].bottom(1)) - p1000.return_tip() # <---------------- Tip Return - - protocol.comment("--> ADDING AMPure (0.8x)") - AMPureVol = 45 - SampleVol = 85 - AMPureMixRep = 5 * 60 if DRYRUN == "NO" else 0.1 * 60 - AMPurePremix = 3 if DRYRUN == "NO" else 1 - # ========NEW SINGLE TIP DISPENSE=========== - for loop, X in enumerate(column_2_list): - p1000.pick_up_tip(TIP_CLEANUP[loop]) # <---------------- Tip Pickup - p1000.mix(AMPurePremix, AMPureVol + 10, AMPure.bottom(z=1)) - p1000.aspirate(AMPureVol, AMPure.bottom(z=1), rate=0.25) - p1000.dispense(AMPureVol, sample_plate_1[X].bottom(z=1), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(sample_plate_1[X].bottom(z=5)) - for Mix in range(2): - p1000.aspirate(60, rate=0.5) - p1000.move_to(sample_plate_1[X].bottom(z=1)) - p1000.aspirate(60, rate=0.5) - p1000.dispense(60, rate=0.5) - p1000.move_to(sample_plate_1[X].bottom(z=5)) - p1000.dispense(30, rate=0.5) - Mix += 1 - p1000.blow_out(sample_plate_1[X].top(z=2)) - p1000.default_speed = 400 - p1000.move_to(sample_plate_1[X].top(z=5)) - p1000.move_to(sample_plate_1[X].top(z=0)) - p1000.move_to(sample_plate_1[X].top(z=5)) - p1000.return_tip() # <---------------- Tip Return - # ========NEW HS MIX========================= - heatershaker.set_and_wait_for_shake_speed(rpm=1800) - protocol.delay(AMPureMixRep) - heatershaker.deactivate_shaker() - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_1, - new_location=mag_block, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - if DRYRUN == "NO": - protocol.delay(minutes=4) - - protocol.comment("--> Removing Supernatant") - RemoveSup = 200 - for loop, X in enumerate(column_2_list): - p1000.pick_up_tip(TIP_CLEANUP[loop]) # <---------------- Tip Pickup - p1000.move_to(sample_plate_1[X].bottom(z=3.5)) - p1000.aspirate(RemoveSup - 100, rate=0.25) - protocol.delay(minutes=0.1) - p1000.move_to(sample_plate_1[X].bottom(z=0.5)) - p1000.aspirate(100, rate=0.25) - p1000.default_speed = 5 - p1000.move_to(sample_plate_1[X].top(z=2)) - p1000.default_speed = 200 - p1000.dispense(200, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p1000.blow_out() - p1000.default_speed = 400 - p1000.move_to(Liquid_trash.top(z=-5)) - p1000.move_to(Liquid_trash.top(z=0)) - p1000.return_tip() # <---------------- Tip Return - - for X in range(2): - protocol.comment("--> ETOH Wash") - ETOHMaxVol = 150 - p1000.pick_up_tip(Tip_ETOH) - for loop, X in enumerate(column_2_list): - p1000.aspirate(ETOHMaxVol, EtOH.bottom(z=1)) - p1000.move_to(EtOH.top(z=0)) - p1000.move_to(EtOH.top(z=-5)) - p1000.move_to(EtOH.top(z=0)) - p1000.move_to(sample_plate_1[X].top(z=-2)) - p1000.dispense(ETOHMaxVol, rate=1) - protocol.delay(minutes=0.1) - p1000.blow_out() - p1000.move_to(sample_plate_1[X].top(z=5)) - p1000.move_to(sample_plate_1[X].top(z=0)) - p1000.move_to(sample_plate_1[X].top(z=5)) - p1000.return_tip() - - if DRYRUN == "NO": - protocol.delay(minutes=0.5) - - protocol.comment("--> Remove ETOH Wash") - for loop, X in enumerate(column_2_list): - p1000.pick_up_tip(TIP_CLEANUP[loop]) # <---------------- Tip Pickup - p1000.move_to(sample_plate_1[X].bottom(z=3.5)) - p1000.aspirate(RemoveSup - 100, rate=0.25) - protocol.delay(minutes=0.1) - p1000.move_to(sample_plate_1[X].bottom(z=0.5)) - p1000.aspirate(100, rate=0.25) - p1000.default_speed = 5 - p1000.move_to(sample_plate_1[X].top(z=2)) - p1000.default_speed = 200 - p1000.dispense(200, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p1000.blow_out() - p1000.default_speed = 400 - p1000.move_to(Liquid_trash.top(z=-5)) - p1000.move_to(Liquid_trash.top(z=0)) - p1000.return_tip() # <---------------- Tip Return - - if DRYRUN == "NO": - protocol.delay(minutes=2) - - protocol.comment("--> Removing Residual ETOH") - for loop, X in enumerate(column_2_list): - p1000.pick_up_tip(TIP_CLEANUP[loop]) # <---------------- Tip Pickup - p1000.move_to(sample_plate_1[X].bottom(z=0)) - p1000.aspirate(50, rate=0.25) - p1000.default_speed = 200 - p1000.dispense(100, Liquid_trash.top(z=0)) - protocol.delay(minutes=0.1) - p1000.blow_out() - p1000.default_speed = 400 - p1000.move_to(Liquid_trash.top(z=-5)) - p1000.move_to(Liquid_trash.top(z=0)) - p1000.return_tip() # <---------------- Tip Return - - if DRYRUN == "NO": - protocol.delay(minutes=1) - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM MAG PLATE TO HEATER SHAKER - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_1, - new_location=heatershaker, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", 1), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - protocol.comment("--> Adding RSB") - RSBVol = 32 - RSBMixRep = 1 * 60 if DRYRUN == "NO" else 0.1 * 60 - for loop, X in enumerate(column_2_list): - p1000.pick_up_tip(TIP_RSB[loop]) # <---------------- Tip Pickup - p1000.aspirate(RSBVol, RSB.bottom(z=1)) - - p1000.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=1.3 * 0.8, y=0, z=-4)))) - p1000.dispense(RSBVol, rate=1) - p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) - p1000.aspirate(RSBVol, rate=1) - p1000.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=0, y=1.3 * 0.8, z=-4)))) - p1000.dispense(RSBVol, rate=1) - p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) - p1000.aspirate(RSBVol, rate=1) - p1000.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=1.3 * -0.8, y=0, z=-4)))) - p1000.dispense(RSBVol, rate=1) - p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) - p1000.aspirate(RSBVol, rate=1) - p1000.move_to((sample_plate_1.wells_by_name()[X].center().move(types.Point(x=0, y=1.3 * -0.8, z=-4)))) - p1000.dispense(RSBVol, rate=1) - p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) - p1000.aspirate(RSBVol, rate=1) - p1000.dispense(RSBVol, rate=1) - - p1000.blow_out(sample_plate_1.wells_by_name()[X].center()) - p1000.move_to(sample_plate_1.wells_by_name()[X].top(z=5)) - p1000.move_to(sample_plate_1.wells_by_name()[X].top(z=0)) - p1000.move_to(sample_plate_1.wells_by_name()[X].top(z=5)) - p1000.return_tip() # <---------------- Tip Return - heatershaker.set_and_wait_for_shake_speed(rpm=1600) - protocol.delay(RSBMixRep) - heatershaker.deactivate_shaker() - - # ============================================================================================ - # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE - heatershaker.open_labware_latch() - protocol.move_labware( - labware=sample_plate_1, - new_location=mag_block, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", 1), - drop_offset=grip_offset("drop", "mag-plate"), - ) - heatershaker.close_labware_latch() - # ============================================================================================ - - if DRYRUN == "NO": - protocol.delay(minutes=3) - - protocol.comment("--> Transferring Supernatant") - TransferSup = 30 - for loop, X in enumerate(column_2_list): - p1000.pick_up_tip(TIP_RSB[loop]) # <---------------- Tip Pickup - p1000.move_to(sample_plate_1[X].bottom(z=0.25)) - p1000.aspirate(TransferSup + 1, rate=0.25) - p1000.dispense(TransferSup + 5, sample_plate_1[column_3_list[loop]].bottom(z=1)) - p1000.return_tip() # <---------------- Tip Return diff --git a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3.py b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3.py index 09c9797d3be..31cf860254c 100644 --- a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3.py +++ b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3.py @@ -58,7 +58,7 @@ def run(protocol: protocol_api.ProtocolContext): # ========== SECOND ROW ========== MAG_PLATE_SLOT = protocol.load_module("magneticBlockV1", "4") reservoir_2 = protocol.load_labware("nest_96_wellplate_2ml_deep", "5") - tiprack_20_1 = protocol.load_labware("opentrons_ot3_96_tiprack_50ul_rss", "6") + tiprack_20_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "6", adapter="opentrons_flex_96_tiprack_adapter") # ========== THIRD ROW =========== if MODULES == True: thermocycler = protocol.load_module("thermocycler module gen2") @@ -67,7 +67,7 @@ def run(protocol: protocol_api.ProtocolContext): thermocycler = 7 sample_plate_1 = protocol.load_labware("nest_96_wellplate_100ul_pcr_full_skirt", "7") reservoir_3 = protocol.load_labware("nest_96_wellplate_2ml_deep", "8") - tiprack_200_1 = protocol.load_labware("opentrons_ot3_96_tiprack_200ul_rss", "9") + tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "9", adapter="opentrons_flex_96_tiprack_adapter") # ========== FOURTH ROW ========== reservoir_4 = protocol.load_labware("nest_96_wellplate_2ml_deep", "11") diff --git a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3.py b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3.py index a1f76f22ec0..babec42887a 100644 --- a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3.py +++ b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3.py @@ -36,20 +36,22 @@ def run(protocol: protocol_api.ProtocolContext): # ========== FIRST ROW =========== heatershaker = protocol.load_module("heaterShakerModuleV1", "1") sample_plate_2 = heatershaker.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") - tiprack_200_1 = protocol.load_labware("opentrons_ot3_96_tiprack_200ul_rss", "2") + tiprack_200_1_adapter = protocol.load_adapter("opentrons_flex_96_tiprack_adapter", "2") + tiprack_200_1 = tiprack_200_1_adapter.load_labware("opentrons_flex_96_tiprack_200ul") temp_block = protocol.load_module("temperature module gen2", "3") sample_plate_3 = temp_block.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") # ========== SECOND ROW ========== MAG_PLATE_SLOT = protocol.load_module("magneticBlockV1", "4") reservoir_1 = protocol.load_labware("nest_96_wellplate_2ml_deep", "5") - reservoir_2 = protocol.load_labware("nest_96_wellplate_2ml_deep", "5") + reservoir_2 = protocol.load_labware("nest_96_wellplate_2ml_deep", "6") # ========== THIRD ROW =========== thermocycler = protocol.load_module("thermocycler module gen2") sample_plate_1 = thermocycler.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") - tiprack_200_2 = protocol.load_labware("opentrons_ot3_96_tiprack_200ul_rss", "8") - reservoir_3 = protocol.load_labware("nest_96_wellplate_2ml_deep", "5") + tiprack_200_2_adapter = protocol.load_adapter("opentrons_flex_96_tiprack_adapter", "8") + tiprack_200_2 = tiprack_200_2_adapter.load_labware("opentrons_flex_96_tiprack_200ul") + reservoir_3 = protocol.load_labware("nest_96_wellplate_2ml_deep", "9") # ========== FOURTH ROW ========== - reservoir_4 = protocol.load_labware("nest_96_wellplate_2ml_deep", "5") + reservoir_4 = protocol.load_labware("nest_96_wellplate_2ml_deep", "11") # =========== RESERVOIR ========== EtOH = reservoir_1["A1"] @@ -221,12 +223,16 @@ def grip_offset(action, item, slot=None): p1000.move_to(Liquid_trash.top(z=0)) p1000.return_tip() # <---------------- Tip Return - protocol.pause("RESET TIPRACKS") - del tiprack_200_1 - del tiprack_200_2 + tiprack_200_3 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", protocol_api.OFF_DECK) + tiprack_20_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK) - tiprack_200_3 = protocol.load_labware("opentrons_ot3_96_tiprack_200ul_rss", "2") - tiprack_20_1 = protocol.load_labware("opentrons_ot3_96_tiprack_50ul_rss", "8") + protocol.move_labware(labware=tiprack_200_1, new_location=protocol_api.OFF_DECK) + + protocol.move_labware(labware=tiprack_200_2, new_location=protocol_api.OFF_DECK) + + protocol.move_labware(labware=tiprack_200_3, new_location=tiprack_200_1_adapter) + + protocol.move_labware(labware=tiprack_20_1, new_location=tiprack_200_2_adapter) for X in range(2): protocol.comment("--> ETOH Wash") diff --git a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction.py b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction.py index 498aa01d627..34e0feb87df 100644 --- a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction.py +++ b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction.py @@ -80,8 +80,8 @@ def run(ctx): elution_res = elutionplate.wells()[0] # Load tips - tips = ctx.load_labware("opentrons_ot3_96_tiprack_200ul_rss", "10").wells()[0] - tips1 = ctx.load_labware("opentrons_ot3_96_tiprack_200ul_rss", "11").wells()[0] + tips = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "10", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + tips1 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "11", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] # load 96 channel pipette pip = ctx.load_instrument("flex_96channel_1000", mount="left") diff --git a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction.py b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction.py deleted file mode 100644 index 1d36403234e..00000000000 --- a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction.py +++ /dev/null @@ -1,494 +0,0 @@ -from opentrons.types import Point -import json -import os -import math -from time import sleep -from opentrons import types -import numpy as np - -""" -Setup: - -Slot 1 = H-S with Nest DW (empty plate) -Slot 2 = Nest DW (350 ul each well) -Slot 3 = Temp Mod with Armadillo PCR plate (100ul each well) -Slot 4 = Magblock (empty) -Slot 5 = Nest DW (300 ul each well) -Slot 6 = Nest DW (empty plate) -Slot 7 = Nest DW (1300 ul each well) -Slot 8 = Nest DW (700 ul each well) -Slot 9 = Nest DW (500 ul each well) -Slot 10 = 1000ul tips -Slot 11 = 1000ul tips (only used during elution steps) - -""" - -metadata = { - "protocolName": "Omega HDQ DNA Extraction: Bacteria 96 FOR ABR TESTING", - "author": "Zach Galluzzo ", -} - -requirements = { - "robotType": "OT-3", - "apiLevel": "2.15", -} - -dry_run = True -HS_SLOT = 1 -USE_GRIPPER = True - - -# Start protocol -def run(ctx): - """ - Here is where you can change the locations of your labware and modules - (note that this is the recommended configuration) - """ - - # *****If drying beads does not produce same results- can eliminate waste in slot 12 and add extra elution reservoir*** - - # Same for all HDQ Extractions - deepwell_type = "nest_96_wellplate_2ml_deep" - wash_vol = 600 - settling_time = 2 - num_washes = 3 - - h_s = ctx.load_module("heaterShakerModuleV1", HS_SLOT) - TL_plate = h_s.load_labware(deepwell_type) # can be whatever plate type - TL_samples = TL_plate.wells()[0] - sample_plate = ctx.load_labware(deepwell_type, "6") - samples_m = sample_plate.wells()[0] - - temp = ctx.load_module("temperature module gen2", "3") - elutionplate = temp.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") - elution_res = elutionplate.wells()[0] - MAG_PLATE_SLOT = ctx.load_module("magneticBlockV1", "4") - # elution_two = ctx.load_labware(deepwell_type, '12').wells()[0] - - TL_res = ctx.load_labware(deepwell_type, "2").wells()[0] - AL_res = ctx.load_labware(deepwell_type, "5").wells()[0] - wash1_res = ctx.load_labware(deepwell_type, "7").wells()[0] - wash2_res = ctx.load_labware(deepwell_type, "8").wells()[0] - bind_res = ctx.load_labware(deepwell_type, "9").wells()[0] - - # Load tips - tips = ctx.load_labware("opentrons_ot3_96_tiprack_1000ul_rss", "10").wells()[0] - tips1 = ctx.load_labware("opentrons_ot3_96_tiprack_1000ul_rss", "11").wells()[0] - - # Differences between sample types - AL_vol = 230 - TL_vol = 270 - sample_vol = 200 - inc_temp = 55 - starting_vol = AL_vol + sample_vol - binding_buffer_vol = 340 - elution_two_vol = 350 - elution_vol = 100 - - # load 96 channel pipette - pip = ctx.load_instrument("flex_96channel_1000", mount="left") - - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 150 - pip.flow_rate.blow_out = 300 - - def resuspend_pellet(vol, plate, reps=3): - pip.flow_rate.aspirate = 150 - pip.flow_rate.dispense = 200 - - loc1 = plate.bottom().move(types.Point(x=1, y=0, z=1)) - loc2 = plate.bottom().move(types.Point(x=0.75, y=0.75, z=1)) - loc3 = plate.bottom().move(types.Point(x=0, y=1, z=1)) - loc4 = plate.bottom().move(types.Point(x=-0.75, y=0.75, z=1)) - loc5 = plate.bottom().move(types.Point(x=-1, y=0, z=1)) - loc6 = plate.bottom().move(types.Point(x=-0.75, y=0 - 0.75, z=1)) - loc7 = plate.bottom().move(types.Point(x=0, y=-1, z=1)) - loc8 = plate.bottom().move(types.Point(x=0.75, y=-0.75, z=1)) - - if vol > 1000: - vol = 1000 - - mixvol = vol * 0.9 - - for _ in range(reps): - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc1) - pip.aspirate(mixvol, loc2) - pip.dispense(mixvol, loc2) - pip.aspirate(mixvol, loc3) - pip.dispense(mixvol, loc3) - pip.aspirate(mixvol, loc4) - pip.dispense(mixvol, loc4) - pip.aspirate(mixvol, loc5) - pip.dispense(mixvol, loc5) - pip.aspirate(mixvol, loc6) - pip.dispense(mixvol, loc6) - pip.aspirate(mixvol, loc7) - pip.dispense(mixvol, loc7) - pip.aspirate(mixvol, loc8) - pip.dispense(mixvol, loc8) - if _ == reps - 1: - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 30 - pip.aspirate(mixvol, loc8) - pip.dispense(mixvol, loc8) - - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 150 - - def bead_mix(vol, plate, reps=5): - pip.flow_rate.aspirate = 150 - pip.flow_rate.dispense = 200 - - loc1 = plate.bottom().move(types.Point(x=0, y=0, z=1)) - loc2 = plate.bottom().move(types.Point(x=0, y=0, z=8)) - loc3 = plate.bottom().move(types.Point(x=0, y=0, z=16)) - loc4 = plate.bottom().move(types.Point(x=0, y=0, z=24)) - - if vol > 1000: - vol = 1000 - - mixvol = vol * 0.9 - - for _ in range(reps): - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc1) - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc2) - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc3) - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc4) - if _ == reps - 1: - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 30 - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc1) - - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 150 - - def reset_protocol(): - ctx.comment("Move TL Sample Plate Back to Heater-Shaker") - h_s.open_labware_latch() - ctx.move_labware( - TL_plate, - h_s, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "deck"), - drop_offset=grip_offset("drop", "heater-shaker", slot=HS_SLOT), - ) - h_s.close_labware_latch() - ctx.comment("Move Sample Plate back to Original Deck Slot") - ctx.move_labware( - sample_plate, - 6, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "deck"), - ) - - pip.pick_up_tip(tips) - # Return Washes 1 and 2 from TL res to Wash res - for r in range(2): - if r == 0: - pip.aspirate(wash_vol, TL_res.top(-20)) - else: - pip.aspirate(wash_vol, TL_res.bottom(1)) - pip.dispense(wash_vol, wash1_res) - pip.air_gap(5) - - # Return sample TL from Bind to TL - pip.aspirate(200, bind_res.top(-19)) - pip.dispense(200, TL_res) - pip.air_gap(5) - - # Return sample TL from TL sample plate to TL res - pip.aspirate(70, TL_samples.bottom()) - pip.dispense(70, TL_res) - pip.air_gap(5) - - # Return AL from Bind to AL - pip.aspirate(AL_vol, bind_res.top(-25)) - pip.dispense(AL_vol, AL_res) - pip.air_gap(5) - - # Return W3 from Bind to W3 - pip.aspirate(wash_vol, bind_res.bottom()) - pip.dispense(wash_vol, wash2_res) - pip.air_gap(5) - - pip.return_tip() - - def grip_offset(action, item, slot=None): - from opentrons.types import Point - - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _pick_up_offsets = { - "deck": Point(), - "mag-plate": Point(), - "heater-shaker": Point(), - "temp-module": Point(), - "thermo-cycler": Point(), - } - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _drop_offsets = { - "deck": Point(z=0.5), - "mag-plate": Point(z=0.5), - "heater-shaker": Point(z=0.5), - "temp-module": Point(z=0.5), - "thermo-cycler": Point(z=0.5), - } - # do NOT edit these values - # NOTE: these values will eventually be in our software - # and will not need to be inside a protocol - _hw_offsets = { - "deck": Point(), - "mag-plate": Point(), - "heater-shaker": Point(z=2.5), - "temp-module": Point(z=5), - "thermo-cycler": Point(z=2.5), - } - # make sure arguments are correct - action_options = ["pick-up", "drop"] - item_options = list(_hw_offsets.keys()) - - if action not in action_options: - raise ValueError(f'"{action}" not recognized, available options: {action_options}') - if item not in item_options: - raise ValueError(f'"{item}" not recognized, available options: {item_options}') - hw_offset = _hw_offsets[item] - if action == "pick-up": - offset = hw_offset + _pick_up_offsets[item] - else: - offset = hw_offset + _drop_offsets[item] - # convert from Point() to dict() - return {"x": offset.x, "y": offset.y, "z": offset.z} - - # Just in case - h_s.close_labware_latch() - for loop in range(3): - # Start Protocol - pip.pick_up_tip(tips) - # Mix PK and TL buffers - ctx.comment("----- Mixing TL buffer and PK -----") - for m in range(3): - pip.aspirate(TL_vol, TL_res) - pip.dispense(TL_vol, TL_res.bottom(30)) - # Transfer TL to plate - ctx.comment("----- Transferring TL and PK to samples -----") - pip.aspirate(TL_vol, TL_res) - pip.air_gap(10) - pip.dispense(pip.current_volume, TL_samples) - h_s.set_target_temperature(55) - ctx.comment("----- Mixing TL buffer with samples -----") - resuspend_pellet(TL_vol, TL_samples, reps=4) - pip.return_tip() - - ctx.comment("----- Mixing and incubating for 30 minutes on Heater-Shaker -----") - h_s.set_and_wait_for_shake_speed(2000) - ctx.delay(minutes=30 if not dry_run else 0.25, msg="Shake at 2000 rpm for 30 minutes to allow lysis.") - h_s.deactivate_shaker() - - # Transfer 200ul of sample + TL buffer to sample plate - ctx.comment("----- Mixing, then transferring 200 ul of sample to new deep well plate -----") - pip.pick_up_tip(tips) - pip.aspirate(sample_vol, TL_samples) - pip.air_gap(20) - pip.dispense(pip.current_volume, samples_m) - pip.blow_out() - pip.return_tip() - - # Move TL samples off H-S into deck slot and sample plate onto H-S - ctx.comment("------- Transferring TL and Sample plates -------") - # Transfer TL samples from H-S to Magnet - h_s.open_labware_latch() - ctx.move_labware( - TL_plate, - MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", slot=HS_SLOT), - drop_offset=grip_offset("drop", "mag-plate"), - ) - # Move sample plate onto H-S from deck - ctx.move_labware( - sample_plate, - h_s, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "deck"), - drop_offset=grip_offset("drop", "heater-shaker", slot=HS_SLOT), - ) - h_s.close_labware_latch() - # Move plate off magplate onto the deck - ctx.move_labware( - TL_plate, - 6, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "deck"), - ) - - # Transfer and mix AL_lysis - ctx.comment("------- Starting AL Lysis Steps -------") - pip.pick_up_tip(tips) - pip.aspirate(AL_vol, AL_res) - pip.air_gap(10) - pip.dispense(pip.current_volume, samples_m) - resuspend_pellet(starting_vol, samples_m, reps=4) - pip.drop_tip(tips) - - # Mix, then heat - h_s.set_and_wait_for_shake_speed(2000) - ctx.delay(minutes=4 if not dry_run else 0.25, msg="Please wait 4 minutes to allow for proper lysis mixing.") - - h_s.deactivate_shaker() - - # Transfer and mix bind&beads - ctx.comment("------- Mixing and Transferring Beads and Binding -------") - pip.pick_up_tip(tips) - bead_mix(binding_buffer_vol, bind_res, reps=3) - pip.aspirate(binding_buffer_vol, bind_res) - pip.dispense(binding_buffer_vol, samples_m) - bead_mix(binding_buffer_vol + starting_vol, samples_m, reps=3) - pip.return_tip() - pip.home() - - # Shake for binding incubation - h_s.set_and_wait_for_shake_speed(rpm=1800) - ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please allow 10 minutes for the beads to bind the DNA.") - - h_s.deactivate_shaker() - - h_s.open_labware_latch() - # Transfer plate to magnet - ctx.move_labware( - sample_plate, - MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", slot=HS_SLOT), - drop_offset=grip_offset("drop", "mag-plate"), - ) - h_s.close_labware_latch() - - ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") - - # Remove Supernatant and move off magnet - ctx.comment("------- Removing Supernatant -------") - pip.pick_up_tip(tips) - pip.aspirate(1000, samples_m.bottom(0.5)) - pip.dispense(1000, bind_res) - if starting_vol + binding_buffer_vol > 1000: - pip.aspirate(1000, samples_m.bottom(0.5)) - pip.dispense(1000, bind_res) - pip.return_tip() - - # Transfer plate from magnet to H/S - h_s.open_labware_latch() - ctx.move_labware( - sample_plate, - h_s, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", slot=HS_SLOT), - ) - - h_s.close_labware_latch() - - # Washes - for i in range(num_washes): - if i == 0 or 1: - wash_res = wash1_res - waste_res = TL_res - if i == 2: - wash_res = wash2_res - waste_res = bind_res - ctx.comment("------- Starting Wash #" + str(i + 1) + " -------") - pip.pick_up_tip(tips) - pip.aspirate(wash_vol, wash_res) - pip.dispense(wash_vol, samples_m) - # resuspend_pellet(wash_vol,samples_m,reps=1) - pip.blow_out() - pip.air_gap(10) - pip.return_tip() - pip.home() - - h_s.set_and_wait_for_shake_speed(rpm=1800) - ctx.delay(minutes=5 if not dry_run else 0.25) - h_s.deactivate_shaker() - h_s.open_labware_latch() - - # Transfer plate to magnet - ctx.move_labware( - sample_plate, - MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", slot=HS_SLOT), - drop_offset=grip_offset("drop", "mag-plate"), - ) - - ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") - - # Remove Supernatant and move off magnet - ctx.comment("------- Removing Supernatant -------") - pip.pick_up_tip(tips) - pip.aspirate(1000, samples_m.bottom(0.5)) - pip.dispense(1000, waste_res.top()) - if wash_vol > 1000: - pip.aspirate(1000, samples_m.bottom(0.5)) - pip.dispense(1000, waste_res.top()) - pip.return_tip() - - # if i == 0 or 2 and not dry_run: - # Transfer plate from magnet to H/S after first two washes - ctx.move_labware( - sample_plate, - h_s, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", slot=HS_SLOT), - ) - h_s.close_labware_latch() - - dry_beads = 10 - - for beaddry in np.arange(dry_beads, 0, -0.5): - ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") - - # Elution - ctx.comment("------- Beginning Elution Steps -------") - - pip.pick_up_tip(tips1) - pip.aspirate(elution_vol, elution_res) - pip.dispense(elution_vol, samples_m) - resuspend_pellet(elution_vol, samples_m, reps=3) - pip.return_tip() - pip.home() - - h_s.set_and_wait_for_shake_speed(rpm=2000) - ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please wait 5 minutes to allow dna to elute from beads.") - h_s.deactivate_shaker() - h_s.open_labware_latch() - - # Transfer plate to magnet - ctx.move_labware( - sample_plate, - MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", slot=HS_SLOT), - drop_offset=grip_offset("drop", "mag-plate"), - ) - - ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") - - pip.pick_up_tip(tips1) - pip.aspirate(elution_vol, samples_m) - pip.dispense(elution_vol, elution_res) - pip.return_tip() - - pip.home() - - reset_protocol() diff --git a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction.py b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction.py deleted file mode 100644 index 9600944a1a6..00000000000 --- a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction.py +++ /dev/null @@ -1,514 +0,0 @@ -from opentrons.types import Point -import json -import os -import math -import threading -from time import sleep -from opentrons import types -import numpy as np - -metadata = { - "protocolName": "Quick Zymo Magbead RNA Extraction with Lysis: Bacteria 96 Channel Deletion Test", - "author": "Zach Galluzzo ", -} - -requirements = { - "robotType": "OT-3", - "apiLevel": "2.15", -} - -""" - ********** - - Line 254 - - - NOTE: this accesses private members of the protocol engine and is not stable, - as soon as moving labware offDeck is supported from the top level `move_labware`, - this hack should be removed - - *********** - -""" - -HS_SLOT = 1 -dry_run = False -USE_GRIPPER = True -whichwash = 1 - - -def run(ctx): - """ - Here is where you can change the locations of your labware and modules - (note that this is the recommended configuration) - """ - # Protocol Parameters - deepwell_type = "nest_96_wellplate_2ml_deep" - if not dry_run: - settling_time = 3 - else: - settling_time = 0.25 - # Volumes Defined - lysis_vol = 100 - binding_buffer_vol = 215 # Beads+Binding - wash_vol = stop_vol = 250 - dnase_vol = 50 - elution_vol = 50 - starting_vol = 250 # This is sample volume (300 in shield) + lysis volume - - h_s = ctx.load_module("heaterShakerModuleV1", HS_SLOT) - sample_plate = h_s.load_labware("opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep") - samples_m = sample_plate.wells()[0] - h_s.close_labware_latch() - MAG_PLATE_SLOT = ctx.load_module("magneticBlockV1", "4") - - tempdeck = ctx.load_module("Temperature Module Gen2", "3") - # Keep elution warm during protocol - elutionplate = tempdeck.load_labware("opentrons_96_pcr_adapter_armadillo_wellplate_200ul") - - # Load Reservoir Plates - wash2_reservoir = lysis_reservoir = ctx.load_labware(deepwell_type, "2") # deleted after use- replaced (by gripper) with wash2 res - bind_reservoir = ctx.load_labware(deepwell_type, "6") - wash1_reservoir = ctx.load_labware(deepwell_type, "5") - wash3_reservoir = wash4_reservoir = wash5_reservoir = ctx.load_labware(deepwell_type, "7") - dnase_reservoir = ctx.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "9") - stop_reservoir = ctx.load_labware(deepwell_type, "8") - - # Load Reagents - lysis_res = lysis_reservoir.wells()[0] # deleted after use- replaced (by gripper) with wash2 res - bind_res = bind_reservoir.wells()[0] - wash1 = wash1_reservoir.wells()[0] - wash2 = wash2_reservoir.wells()[0] # loaded on magplate- move to lysis location after lysis is used - wash3 = wash4 = wash5 = wash3_reservoir.wells()[0] - dnase_res = dnase_reservoir.wells()[0] - stop_res = stop_reservoir.wells()[0] - elution_res = elutionplate.wells()[0] - - # Load tips - tips = ctx.load_labware("opentrons_ot3_96_tiprack_1000ul_rss", "10").wells()[0] - tips1 = ctx.load_labware("opentrons_ot3_96_tiprack_1000ul_rss", "11").wells()[0] - - # load instruments - pip = ctx.load_instrument("flex_96channel_1000", mount="left") - - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 150 - pip.flow_rate.blow_out = 300 - - def grip_offset(action, item, slot=None): - from opentrons.types import Point - - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _pick_up_offsets = { - "deck": Point(), - "mag-plate": Point(), - "heater-shaker": Point(), - "temp-module": Point(), - "thermo-cycler": Point(), - } - # EDIT these values - # NOTE: we are still testing to determine our software's defaults - # but we also expect users will want to edit these - _drop_offsets = { - "deck": Point(z=0.5), - "mag-plate": Point(z=0.5), - "heater-shaker": Point(z=0.5), - "temp-module": Point(z=0.5), - "thermo-cycler": Point(z=0.5), - } - # do NOT edit these values - # NOTE: these values will eventually be in our software - # and will not need to be inside a protocol - _hw_offsets = { - "deck": Point(), - "mag-plate": Point(), - "heater-shaker": Point(z=2.5), - "temp-module": Point(z=5), - "thermo-cycler": Point(z=2.5), - } - # make sure arguments are correct - action_options = ["pick-up", "drop"] - item_options = list(_hw_offsets.keys()) - - if action not in action_options: - raise ValueError(f'"{action}" not recognized, available options: {action_options}') - if item not in item_options: - raise ValueError(f'"{item}" not recognized, available options: {item_options}') - hw_offset = _hw_offsets[item] - if action == "pick-up": - offset = hw_offset + _pick_up_offsets[item] - else: - offset = hw_offset + _drop_offsets[item] - # convert from Point() to dict() - return {"x": offset.x, "y": offset.y, "z": offset.z} - - def blink(): - for i in range(3): - ctx.set_rail_lights(True) - ctx.delay(minutes=0.01666667) - ctx.set_rail_lights(False) - ctx.delay(minutes=0.01666667) - - def remove_supernatant(vol, waste): - pip.pick_up_tip(tips) - if vol > 1000: - x = 2 - else: - x = 1 - transfer_vol = vol / x - for i in range(x): - pip.aspirate(transfer_vol, samples_m.bottom(0.15)) - pip.dispense(transfer_vol, waste) - pip.return_tip() - - # Transfer plate from magnet to H/S - h_s.open_labware_latch() - ctx.move_labware( - sample_plate, - h_s, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", slot=HS_SLOT), - ) - h_s.close_labware_latch() - - def resuspend_pellet(vol, plate, reps=3): - pip.flow_rate.aspirate = 150 - pip.flow_rate.dispense = 200 - - loc1 = plate.bottom().move(types.Point(x=1, y=0, z=1)) - loc2 = plate.bottom().move(types.Point(x=0.75, y=0.75, z=1)) - loc3 = plate.bottom().move(types.Point(x=0, y=1, z=1)) - loc4 = plate.bottom().move(types.Point(x=-0.75, y=0.75, z=1)) - loc5 = plate.bottom().move(types.Point(x=-1, y=0, z=1)) - loc6 = plate.bottom().move(types.Point(x=-0.75, y=0 - 0.75, z=1)) - loc7 = plate.bottom().move(types.Point(x=0, y=-1, z=1)) - loc8 = plate.bottom().move(types.Point(x=0.75, y=-0.75, z=1)) - - if vol > 1000: - vol = 1000 - - mixvol = vol * 0.9 - - for _ in range(reps): - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc1) - pip.aspirate(mixvol, loc2) - pip.dispense(mixvol, loc2) - pip.aspirate(mixvol, loc3) - pip.dispense(mixvol, loc3) - pip.aspirate(mixvol, loc4) - pip.dispense(mixvol, loc4) - pip.aspirate(mixvol, loc5) - pip.dispense(mixvol, loc5) - pip.aspirate(mixvol, loc6) - pip.dispense(mixvol, loc6) - pip.aspirate(mixvol, loc7) - pip.dispense(mixvol, loc7) - pip.aspirate(mixvol, loc8) - pip.dispense(mixvol, loc8) - if _ == reps - 1: - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 30 - pip.aspirate(mixvol, loc8) - pip.dispense(mixvol, loc8) - - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 150 - - def bead_mix(vol, plate, reps=5): - pip.flow_rate.aspirate = 150 - pip.flow_rate.dispense = 200 - - loc1 = plate.bottom().move(types.Point(x=0, y=0, z=1)) - loc2 = plate.bottom().move(types.Point(x=0, y=0, z=8)) - loc3 = plate.bottom().move(types.Point(x=0, y=0, z=16)) - loc4 = plate.bottom().move(types.Point(x=0, y=0, z=24)) - - if vol > 1000: - vol = 1000 - - mixvol = vol * 0.9 - - for _ in range(reps): - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc1) - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc2) - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc3) - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc4) - if _ == reps - 1: - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 30 - pip.aspirate(mixvol, loc1) - pip.dispense(mixvol, loc1) - - pip.flow_rate.aspirate = 50 - pip.flow_rate.dispense = 150 - - def lysis(vol, source): - pip.pick_up_tip(tips) - pip.aspirate(vol, source) - pip.dispense(vol, samples_m) - resuspend_pellet(starting_vol, samples_m, reps=5 if not dry_run else 1) - pip.return_tip() - - h_s.set_and_wait_for_shake_speed(rpm=2000) - - # Delete Lysis reservoir from deck - """ - blink() - ctx.pause('Please remove lysis reservoir (slot 2 or D2) from the deck.') - del ctx.deck['2'] - - - ctx._core._engine_client.move_labware( - labware_id=lysis_reservoir._core.labware_id, - new_location="offDeck", - strategy="manualMoveWithPause", - use_pick_up_location_lpc_offset=False, - use_drop_location_lpc_offset=False, - pick_up_offset=None, - drop_offset=None - ) - - ********** - - - NOTE: this accesses private members of the protocol engine and is not stable, - as soon as moving labware offDeck is supported from the top level `move_labware`, - this hack should be removed - - *********** - - - - - #Transfer wash2 res from magnet to deck slot - ctx.move_labware( - wash2_reservoir, - 2, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up","mag-plate"), - drop_offset=grip_offset("drop","deck") - ) - """ - ctx.delay( - minutes=1 if not dry_run else 0.25, - msg="Please wait 2 minutes while the lysis buffer mixes with the sample.", - ) - h_s.deactivate_shaker() - - def bind(vol, source): - """ - `bind` will perform magnetic bead binding on each sample in the - deepwell plate. Each channel of binding beads will be mixed before - transfer, and the samples will be mixed with the binding beads after - the transfer. The magnetic deck activates after the addition to all - samples, and the supernatant is removed after bead bining. - :param vol (float): The amount of volume to aspirate from the elution - buffer source and dispense to each well containing - beads. - :param park (boolean): Whether to save sample-corresponding tips - between adding elution buffer and transferring - supernatant to the final clean elutions PCR - plate. - """ - pip.pick_up_tip(tips) - # Mix in reservoir - bead_mix(vol, source, reps=5 if not dry_run else 1) - # Transfer from reservoir - pip.aspirate(vol, source) - pip.dispense(vol, samples_m) - # Mix in plate - bead_mix(1000, samples_m, reps=8 if not dry_run else 1) - pip.return_tip() - - h_s.set_and_wait_for_shake_speed(rpm=1800) - ctx.delay(minutes=20 if not dry_run else 0.25, msg="Please wait 20 minutes while the sample binds with the beads.") - h_s.deactivate_shaker() - - h_s.open_labware_latch() - # Transfer plate to magnet - ctx.move_labware( - sample_plate, - MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", slot=HS_SLOT), - drop_offset=grip_offset("drop", "mag-plate"), - ) - h_s.close_labware_latch() - - for bindi in np.arange(settling_time + 1, 0, -0.5): # Settling time delay with countdown timer - ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") - - # remove initial supernatant - remove_supernatant(vol + starting_vol, bind_res) - - def wash(vol, source): - global whichwash # Defines which wash the protocol is on to log on the app - - if source == wash1: - whichwash = 1 - waste_res = bind_res - if source == wash2: - whichwash = 2 - waste_res = bind_res - if source == wash3: - whichwash = 3 - waste_res = wash2 - if source == wash4: - whichwash = 4 - waste_res = wash2 - if source == wash5: - whichwash = 5 - waste_res = wash2 - - pip.pick_up_tip(tips) - pip.aspirate(vol, source) - pip.dispense(vol, samples_m) - resuspend_pellet(vol, samples_m, reps=5 if not dry_run else 1) - pip.return_tip() - - h_s.set_and_wait_for_shake_speed(2000) - ctx.delay(minutes=2 if not dry_run else 0.25, msg="Please allow 2 minutes for wash to mix on heater-shaker.") - h_s.deactivate_shaker() - - h_s.open_labware_latch() - # Transfer plate to magnet - ctx.move_labware( - sample_plate, - MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", slot=HS_SLOT), - drop_offset=grip_offset("drop", "mag-plate"), - ) - h_s.close_labware_latch() - - for washi in np.arange(settling_time, 0, -0.5): # settling time timer for washes - ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in wash " + str(whichwash) + " incubation.") - - remove_supernatant(vol, waste_res) - - def dnase(vol, source): - pip.pick_up_tip(tips) - pip.aspirate(vol, source) - pip.dispense(vol, samples_m) - resuspend_pellet(vol, samples_m, reps=4 if not dry_run else 1) - pip.return_tip() - - h_s.set_and_wait_for_shake_speed(rpm=2000) - if not dry_run: - h_s.set_and_wait_for_temperature(65) - # minutes should equal 10 minus time it takes to reach 65 - ctx.delay(minutes=9 if not dry_run else 0.25, msg="Please wait 10 minutes while the dnase incubates.") - h_s.deactivate_shaker() - - def stop_reaction(vol, source): - pip.pick_up_tip(tips) - pip.aspirate(vol, source) - pip.dispense(vol, samples_m) - resuspend_pellet(vol, samples_m, reps=2 if not dry_run else 1) - pip.return_tip() - - h_s.set_and_wait_for_shake_speed(rpm=1800) - ctx.delay( - minutes=10 if not dry_run else 0.1, - msg="Please wait 10 minutes while the stop solution inactivates the dnase.", - ) - h_s.deactivate_shaker() - - h_s.open_labware_latch() - # Transfer plate to magnet - ctx.move_labware( - sample_plate, - MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", slot=HS_SLOT), - drop_offset=grip_offset("drop", "mag-plate"), - ) - h_s.close_labware_latch() - - for stop in np.arange(settling_time, 0, -0.5): - ctx.delay(minutes=0.5, msg="There are " + str(stop) + " minutes left in this incubation.") - - remove_supernatant(vol + 50, wash1) - - def elute(vol, source): - pip.pick_up_tip(tips1) - # Transfer - pip.aspirate(vol, source) - pip.dispense(vol, samples_m) - # Mix - resuspend_pellet(vol, samples_m, reps=3 if not dry_run else 1) - pip.return_tip() - - # Elution Incubation - h_s.set_and_wait_for_shake_speed(rpm=2000) - if not dry_run: - tempdeck.set_temperature(4) - ctx.delay(minutes=3 if not dry_run else 0.25, msg="Please wait 5 minutes while the sample elutes from the beads.") - h_s.deactivate_shaker() - - h_s.open_labware_latch() - # Transfer plate to magnet - ctx.move_labware( - sample_plate, - MAG_PLATE_SLOT, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "heater-shaker", slot=HS_SLOT), - drop_offset=grip_offset("drop", "mag-plate"), - ) - h_s.close_labware_latch() - - for elutei in np.arange(settling_time, 0, -0.5): - ctx.delay(minutes=0.5, msg="Incubating on MagDeck for " + str(elutei) + " more minutes.") - - pip.flow_rate.aspirate = 25 - - pip.pick_up_tip(tips1) - pip.aspirate(vol, samples_m) - pip.dispense(vol, source) - pip.return_tip() - - h_s.open_labware_latch() - # Transfer plate to magnet - ctx.move_labware( - sample_plate, - h_s, - use_gripper=USE_GRIPPER, - pick_up_offset=grip_offset("pick-up", "mag-plate"), - drop_offset=grip_offset("drop", "heater-shaker", slot=HS_SLOT), - ) - h_s.close_labware_latch() - - """ - Here is where you can call the methods defined above to fit your specific - protocol. The normal sequence is: - """ - # Start Protocol - for x in range(2): - lysis(lysis_vol, lysis_res) - bind(binding_buffer_vol, bind_res) - wash(wash_vol, wash1) - if not dry_run: - wash(wash_vol, wash2) - wash(wash_vol, wash3) - # dnase1 treatment - dnase(dnase_vol, dnase_res) - stop_reaction(stop_vol, stop_res) - # Resume washes - if not dry_run: - wash(wash_vol, wash4) - wash(wash_vol, wash5) - tempdeck.set_temperature(55) - drybeads = 9 # Number of minutes you want to dry for - else: - drybeads = 0.5 - for beaddry in np.arange(drybeads, 0, -0.5): - ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") - elute(elution_vol, elution_res) diff --git a/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria.py b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria.py new file mode 100644 index 00000000000..bb037c9beb2 --- /dev/null +++ b/app-testing/files/protocols/Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria.py @@ -0,0 +1,354 @@ +from opentrons.types import Point +import json +import os +import math +from time import sleep +from opentrons import types +import numpy as np + +metadata = { + "protocolName": "Flex ZymoBIOMICS Magbead DNA Extraction: Cells or Bacteria 96 channel", + "author": "Zach Galluzzo ", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.15", +} + +dry_run = False +HS_SLOT = 1 +USE_GRIPPER = True + +ABR_TEST = True +if ABR_TEST == True: + DRYRUN = True # True = skip incubation times, shorten mix, for testing purposes + TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack +else: + DRYRUN = False # True = skip incubation times, shorten mix, for testing purposes + TIP_TRASH = True + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + deepwell_type = "nest_96_wellplate_2ml_deep" + wash1_vol = 500 + wash2_vol = wash3_vol = wash4_vol = 900 + if not dry_run: + settling_time = 2 + lysis_incubation = 30 + if dry_run: + settling_time = 0.25 + lysis_incubation = 0.25 + + h_s = ctx.load_module("heaterShakerModuleV1", HS_SLOT) + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") + samples_m = sample_plate.wells()[0] + + temp = ctx.load_module("temperature module gen2", "D3") + tempblock = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = tempblock.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate/ Reservoir") + magblock = ctx.load_module("magneticBlockV1", "C1") + + lysis_res = ctx.load_labware(deepwell_type, "D2", "Lysis reservoir").wells()[0] + bind_res = ctx.load_labware(deepwell_type, "C2", "Beads and binding reservoir").wells()[0] + bind2_res = ctx.load_labware(deepwell_type, "C3", "Binding 2 reservoir").wells()[0] + wash1_res = ctx.load_labware(deepwell_type, "B1", "Wash 1 reservoir").wells()[0] + wash2_res = ctx.load_labware(deepwell_type, "B2", "Wash 2 reservoir").wells()[0] + wash4_res = ctx.load_labware(deepwell_type, "B3", "Wash 3 reservoir").wells()[0] + elution_res = elutionplate.wells()[0] + + # Load tips + tips = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + tips1 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + + # Differences between sample types + lysis_vol = 220 # Shield + sample_vol = 10 + starting_vol = lysis_vol + sample_vol + binding_buffer_vol = 625 + bind2_vol = 500 + elution_vol = 75 + + # load 96 channel pipette + pip = ctx.load_instrument("flex_96channel_1000", mount="left") + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + pip.flow_rate.blow_out = 300 + + def resuspend_pellet(vol, plate, reps=3): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=1, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0.75, y=0.75, z=1)) + loc3 = plate.bottom().move(types.Point(x=0, y=1, z=1)) + loc4 = plate.bottom().move(types.Point(x=-0.75, y=0.75, z=1)) + loc5 = plate.bottom().move(types.Point(x=-1, y=0, z=1)) + loc6 = plate.bottom().move(types.Point(x=-0.75, y=0 - 0.75, z=1)) + loc7 = plate.bottom().move(types.Point(x=0, y=-1, z=1)) + loc8 = plate.bottom().move(types.Point(x=0.75, y=-0.75, z=1)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc2) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc3) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc4) + pip.dispense(mixvol, loc4) + pip.aspirate(mixvol, loc5) + pip.dispense(mixvol, loc5) + pip.aspirate(mixvol, loc6) + pip.dispense(mixvol, loc6) + pip.aspirate(mixvol, loc7) + pip.dispense(mixvol, loc7) + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + def bead_mix(vol, plate, reps=5): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=0, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0, y=0, z=8)) + loc3 = plate.bottom().move(types.Point(x=0, y=0, z=16)) + loc4 = plate.bottom().move(types.Point(x=0, y=0, z=24)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc4) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + # Just in case + h_s.close_labware_latch() + + # Start Protocol + + # Mix and Transfer Lysis + ctx.comment("------Mix Shield and PK in Reservoir, Transfer to Sample Plate, Mix in Sample Plate-----") + pip.pick_up_tip(tips) + pip.aspirate(lysis_vol, lysis_res) + pip.air_gap(20) + pip.dispense(pip.current_volume, samples_m) + for _ in range(8 if not dry_run else 1): + pip.aspirate(lysis_vol * 0.9, samples_m) + pip.dispense(pip.current_volume, samples_m.bottom(20)) + pip.return_tip() + + # Mix in sample plate + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=lysis_incubation, msg="Please wait 30 minutes to allow for proper lysis mixing.") + h_s.deactivate_shaker() + + # Transfer and mix bind&beads + ctx.comment("------Mixing Beads with Binding, then Transfer to Sample Plate-----") + pip.pick_up_tip(tips) + bead_mix(binding_buffer_vol, bind_res, reps=5 if not dry_run else 1) + pip.aspirate(binding_buffer_vol, bind_res) + pip.dispense(binding_buffer_vol, samples_m) + if binding_buffer_vol + starting_vol < 1000: + mix_vol = binding_buffer_vol + starting_vol + else: + mix_vol = 1000 + bead_mix(mix_vol, samples_m, reps=7 if not dry_run else 1) + pip.return_tip() + + # Shake for binding incubation + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please allow 10 minutes for the beads to bind the DNA.") + h_s.deactivate_shaker() + + ctx.comment("------Moving Plate to Magnet to Begin Pelleting-----") + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + # Remove Supernatant and move off magnet + ctx.comment("------Removing Supernatant-----") + pip.pick_up_tip(tips) + re_vol = binding_buffer_vol + starting_vol + pip.aspirate(re_vol, samples_m.bottom(0.5)) + pip.dispense(re_vol, bind_res) + if re_vol > 1000: + dif = (starting_vol + binding_buffer_vol) - 1000 + pip.aspirate(dif + 50, samples_m.bottom(0.5)) + pip.dispense(dif + 50, bind_res) + pip.return_tip() + + # Transfer plate from magnet to H/S + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter, use_gripper=USE_GRIPPER) + + h_s.close_labware_latch() + + # Quick Bind #2 + ctx.comment("-----Beginning Bind #2-----") + pip.pick_up_tip(tips) + pip.aspirate(bind2_vol, bind2_res) + pip.air_gap(20) + pip.dispense(pip.current_volume, samples_m.top()) + resuspend_pellet(bind2_vol, samples_m, reps=4 if not dry_run else 1) + pip.blow_out() + pip.return_tip() + + ctx.comment("-----Mixing Bind2 with Beads-----") + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=1 if not dry_run else 0.25, msg="Shake at 2000 rpm for 1 minutes.") + h_s.deactivate_shaker() + + ctx.comment("------Moving Plate to Magnet to Begin Pelleting-----") + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + # Remove Supernatant and move off magnet + ctx.comment("------Removing Supernatant-----") + pip.pick_up_tip(tips) + pip.aspirate(bind2_vol, samples_m.bottom(0.5)) + pip.dispense(bind2_vol, bind_res) + pip.return_tip() + + # Transfer plate from magnet to H/S + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + # Washes + for i in range(4 if not dry_run else 1): + if i == 0: + wash_res = wash1_res + wash_vol = wash1_vol + waste_res = bind_res + whichwash = 1 + height = 1 + if i == 1: + wash_res = wash2_res + wash_vol = wash2_vol + waste_res = bind2_res + whichwash = 2 + height = 15 + if i == 2: + wash_res = wash2_res + wash_vol = wash3_vol + waste_res = bind2_res + whichwash = 3 + height = 1 + if i == 3: + wash_res = wash4_res + wash_vol = wash4_vol + waste_res = wash2_res + whichwash = 4 + height = 1 + + ctx.comment("------Beginning Wash #" + str(whichwash) + "-----") + pip.pick_up_tip(tips) + pip.aspirate(wash_vol, wash_res.bottom(height)) + pip.dispense(wash_vol, samples_m) + pip.air_gap(20) + pip.return_tip() + pip.home() + + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=5 if not dry_run else 0.25) + h_s.deactivate_shaker() + h_s.open_labware_latch() + + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + + ctx.delay( + minutes=settling_time, + msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet in wash #" + str(whichwash) + ".", + ) + + # Remove Supernatant and move off magnet + ctx.comment("------Removing Supernatant-----") + pip.pick_up_tip(tips) + pip.aspirate(wash_vol, samples_m.bottom(0.5)) + pip.dispense(wash_vol, waste_res.top()) + + pip.return_tip() + # Transfer plate from magnet to H/S + ctx.move_labware(sample_plate, h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + ctx.comment("------Drying Beads for 10 minutes-----") + + if not dry_run: + h_s.set_and_wait_for_temperature(55) + + drybeads = 9 if not dry_run else 0.5 # Number of minutes you want to dry for + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + + # Elution + ctx.comment("------Beginning Elution-----") + pip.pick_up_tip(tips1) + pip.flow_rate.aspirate = 25 + pip.aspirate(elution_vol, elution_res) + pip.dispense(elution_vol, samples_m) + pip.return_tip() + pip.home() + + h_s.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please wait 5 minutes to allow dna to elute from beads.") + h_s.deactivate_shaker() + h_s.open_labware_latch() + + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + ctx.comment("------Transfer DNA to Final Elution Plate-----") + pip.pick_up_tip(tips1) + pip.aspirate(elution_vol, samples_m.bottom(0.5)) + pip.dispense(elution_vol, elutionplate.wells()[0]) + pip.return_tip() + + pip.home() diff --git a/app-testing/files/protocols/Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2.py b/app-testing/files/protocols/Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2.py deleted file mode 100644 index c08906b2297..00000000000 --- a/app-testing/files/protocols/Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2.py +++ /dev/null @@ -1,921 +0,0 @@ -from opentrons import protocol_api - -import inspect - -metadata = { - "protocolName": "OT3 ABR KAPA Library Quant v2", - "author": "Opentrons ", - "source": "Protocol Library", - "description": "OT3 ABR KAPA Library Quant v2", -} - -requirements = { - "robotType": "OT-3", - "apiLevel": "2.15", -} - - -def right(s, amount): - if s == None: - return None - elif amount == None: - return None # Or throw a missing argument error - s = str(s) - if amount > len(s): - return s - elif amount == 0: - return "" - else: - return s[-amount:] - - -# SCRIPT SETTINGS -DRYRUN = "YES" # YES or NO, DRYRUN = 'YES' will return tips, skip incubation times, shorten mix, for testing purposes -OFFSET = "YES" # YES or NO, Sets whether to use protocol specific z offsets for each tip and labware or no offsets aside from defaults - -# PROTOCOL SETTINGS -SAMPLES = "24x" # 8x, 16x, or 24x -FORMAT = "384" # 96 or 384 -INICOLUMN1 = "A1" -INICOLUMN2 = "A3" -INICOLUMN3 = "A5" - -# PROTOCOL BLOCKS -STEP_DILUTE = 1 -STEP_MIX = 1 -STEP_DISPENSE = 1 - -STEPS = {STEP_DILUTE, STEP_MIX, STEP_DISPENSE} - - -def run(protocol: protocol_api.ProtocolContext): - if DRYRUN == "YES": - protocol.comment("THIS IS A DRY RUN") - else: - protocol.comment("THIS IS A REACTION RUN") - - # DECK SETUP AND LABWARE - protocol.comment("THIS IS A NO MODULE RUN") - source_plate = protocol.load_labware( - "nest_96_wellplate_100ul_pcr_full_skirt", "1" - ) # <--- Actually an Eppendorf 96 well, same dimensions - reservoir = protocol.load_labware("nest_12_reservoir_15ml", "2") - dilution_plate_1 = protocol.load_labware("opentrons_96_aluminumblock_biorad_wellplate_200ul", "3") - - tiprack_50_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "4") - tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "5") - tiprack_50_2 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "6") - - reagent_plate = protocol.load_labware("nest_96_wellplate_100ul_pcr_full_skirt", "7") # <--- NEST Strip Tubes - dilution_plate_2 = protocol.load_labware("opentrons_96_aluminumblock_biorad_wellplate_200ul", "8") - if FORMAT == "96": - qpcrplate_1 = protocol.load_labware( - "nest_96_wellplate_100ul_pcr_full_skirt", "9" - ) # <--- Actually an Eppendorf 96 well, same dimensions - qpcrplate_2 = protocol.load_labware( - "nest_96_wellplate_100ul_pcr_full_skirt", "10" - ) # <--- Actually an Eppendorf 96 well, same dimensions - if FORMAT == "384": - qpcrplate_1 = protocol.load_labware("corning_384_wellplate_112ul_flat", "9") # <--- Actually an Eppendorf 96 well, same dimensions - qpcrplate_2 = protocol.load_labware("corning_384_wellplate_112ul_flat", "10") # <--- Actually an Eppendorf 96 well, same dimensions - - # REAGENT PLATE - STD_1 = reagent_plate["A1"] - STD_2 = reagent_plate["A2"] - PCR_1 = reagent_plate["A3"] - PCR_2 = reagent_plate["A4"] - - # RESERVOIR - DIL = reservoir["A5"] - - # pipette - p50 = protocol.load_instrument("flex_8channel_50", "left", tip_racks=[tiprack_50_1, tiprack_50_2]) - p1000 = protocol.load_instrument("flex_8channel_1000", "right", tip_racks=[tiprack_200_1]) - - # samples - src_file_path = inspect.getfile(lambda: None) - protocol.comment(src_file_path) - - # tip and sample tracking - if SAMPLES == "8x": - protocol.comment("There are 8 Samples") - samplecolumns = 1 - elif SAMPLES == "16x": - protocol.comment("There are 16 Samples") - samplecolumns = 2 - elif SAMPLES == "24x": - protocol.comment("There are 24 Samples") - samplecolumns = 3 - else: - protocol.pause("ERROR?") - - # offset - p1000_offset_Deck = 0 - p1000_offset_Res = 0 - p1000_offset_Tube = 0 - p1000_offset_Thermo = 0 - p1000_offset_Mag = 0 - p1000_offset_Temp = 0 - - p50_offset_Deck = 0 - p50_offset_Res = 0 - p50_offset_Tube = 0 - p50_offset_Thermo = 0 - p50_offset_Mag = 0 - p50_offset_Temp = 0 - - # commands - - if STEP_DILUTE == 1: - protocol.comment("==============================================") - protocol.comment("--> Dispensing Diluent Part 1 and Part 2") - protocol.comment("==============================================") - p1000.pick_up_tip() - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A2" - Y = "A6" - p1000.move_to(DIL.bottom(z=p1000_offset_Res)) - p1000.mix(3, 200, rate=0.5) - p1000.move_to(DIL.top(z=+5)) - protocol.delay(seconds=2) - p1000.aspirate(200, DIL.bottom(z=p1000_offset_Res), rate=0.25) - p1000.dispense(98, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[X].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.dispense(95, dilution_plate_1[Y].bottom(z=p1000_offset_Temp), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[Y].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.move_to(DIL.top()) - p1000.blow_out() - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A3" - Y = "A7" - p1000.move_to(DIL.bottom(z=p1000_offset_Res)) - p1000.mix(3, 200, rate=0.5) - p1000.move_to(DIL.top(z=+5)) - protocol.delay(seconds=2) - p1000.aspirate(200, DIL.bottom(z=p1000_offset_Res), rate=0.25) - p1000.dispense(98, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[X].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.dispense(95, dilution_plate_1[Y].bottom(z=p1000_offset_Temp), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[Y].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.move_to(DIL.top()) - p1000.blow_out() - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A4" - Y = "A8" - p1000.move_to(DIL.bottom(z=p1000_offset_Res)) - p1000.mix(3, 200, rate=0.5) - p1000.move_to(DIL.top(z=+5)) - protocol.delay(seconds=2) - p1000.aspirate(200, DIL.bottom(z=p1000_offset_Res), rate=0.25) - p1000.dispense(98, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[X].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.dispense(95, dilution_plate_1[Y].bottom(z=p1000_offset_Temp), rate=0.25) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[Y].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.move_to(DIL.top()) - p1000.blow_out() - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - protocol.comment("==============================================") - protocol.comment("--> Adding Sample to Diluent Part 1") - protocol.comment("==============================================") - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = INICOLUMN1 - Y = "A2" - p50.pick_up_tip() - p50.aspirate(2, source_plate[X].bottom(z=p50_offset_Mag), rate=0.25) - p50.dispense(2, dilution_plate_1[Y].center(), rate=0.5) - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = INICOLUMN2 - Y = "A3" - p50.pick_up_tip() - p50.aspirate(2, source_plate[X].bottom(z=p50_offset_Mag), rate=0.25) - p50.dispense(2, dilution_plate_1[Y].center(), rate=0.5) - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = INICOLUMN3 - Y = "A4" - p50.pick_up_tip() - p50.aspirate(2, source_plate[X].bottom(z=p50_offset_Mag), rate=0.25) - p50.dispense(2, dilution_plate_1[Y].center(), rate=0.5) - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - - protocol.comment("--> Mixing") - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A2" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - p1000.mix(50, 80) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[X].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A3" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - - p1000.mix(50, 80) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[X].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A4" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - p1000.mix(50, 80) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[X].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - protocol.comment("==============================================") - protocol.comment("--> Adding Diluted Sample to Diluent Part 2") - protocol.comment("==============================================") - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A2" - Y = "A6" - p50.pick_up_tip() - p50.aspirate(5, dilution_plate_1[X].center(), rate=0.5) - p50.dispense(5, dilution_plate_1[Y].center(), rate=0.5) - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A3" - Y = "A7" - p50.pick_up_tip() - p50.aspirate(5, dilution_plate_1[X].center(), rate=0.5) - p50.dispense(5, dilution_plate_1[Y].center(), rate=0.5) - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A4" - Y = "A8" - p50.pick_up_tip() - p50.aspirate(5, dilution_plate_1[X].center(), rate=0.5) - p50.dispense(5, dilution_plate_1[Y].center(), rate=0.5) - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - - protocol.comment("--> Mixing") - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A6" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - p1000.mix(50, 80) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[X].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A7" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - p1000.mix(50, 80) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[X].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A8" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - p1000.mix(50, 80) - p1000.default_speed = 5 - p1000.move_to(dilution_plate_1[X].top()) - protocol.delay(seconds=2) - p1000.default_speed = 400 - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - for repeat in range(6): - if STEP_MIX == 1: - protocol.comment("==============================================") - protocol.comment("--> Adding qPCR Mix") - protocol.comment("==============================================") - qPCRVol = 50 - p1000.pick_up_tip() - p1000.aspirate((qPCRVol), PCR_1.bottom(z=p1000_offset_Thermo), rate=0.25) - p1000.dispense(qPCRVol, dilution_plate_1["A9"].bottom(z=p1000_offset_Temp), rate=0.25) - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A10" - p1000.aspirate((qPCRVol), PCR_1.bottom(z=p1000_offset_Thermo), rate=0.25) - p1000.dispense(qPCRVol, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A11" - p1000.aspirate((qPCRVol), PCR_1.bottom(z=p1000_offset_Thermo), rate=0.25) - p1000.dispense(qPCRVol, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A12" - p1000.aspirate((qPCRVol), PCR_1.bottom(z=p1000_offset_Thermo), rate=0.25) - p1000.dispense(qPCRVol, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - p1000.pick_up_tip() - p1000.aspirate((qPCRVol), PCR_2.bottom(z=p1000_offset_Thermo), rate=0.25) - p1000.dispense(qPCRVol, dilution_plate_2["A9"].bottom(z=p1000_offset_Deck), rate=0.25) - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A10" - p1000.aspirate((qPCRVol), PCR_2.bottom(z=p1000_offset_Thermo), rate=0.25) - p1000.dispense(qPCRVol, dilution_plate_2[X].bottom(z=p1000_offset_Deck), rate=0.25) - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A11" - p1000.aspirate((qPCRVol), PCR_2.bottom(z=p1000_offset_Thermo), rate=0.25) - p1000.dispense(qPCRVol, dilution_plate_2[X].bottom(z=p1000_offset_Deck), rate=0.25) - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A12" - p1000.aspirate((qPCRVol), PCR_2.bottom(z=p1000_offset_Thermo), rate=0.25) - p1000.dispense(qPCRVol, dilution_plate_2[X].bottom(z=p1000_offset_Deck), rate=0.25) - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - protocol.comment("==============================================") - protocol.comment("--> Adding Standards to Mix") - protocol.comment("==============================================") - SampleVol = 12.5 - p50.pick_up_tip() - p50.aspirate(SampleVol, STD_1.bottom(z=p50_offset_Thermo), rate=0.5) - p50.dispense(SampleVol, dilution_plate_1["A9"].bottom(z=p50_offset_Temp), rate=0.5) - p50.default_speed = 2.5 - p50.move_to(dilution_plate_1["A9"].center()) - protocol.delay(seconds=2) - p50.default_speed = 400 - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - - p50.pick_up_tip() - p50.aspirate(SampleVol, STD_2.bottom(z=p50_offset_Thermo), rate=0.5) - p50.dispense(SampleVol, dilution_plate_2["A9"].bottom(z=p50_offset_Deck), rate=0.5) - p50.default_speed = 2.5 - p50.move_to(dilution_plate_2["A9"].center()) - protocol.delay(seconds=2) - p50.default_speed = 400 - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - - protocol.comment("==============================================") - protocol.comment("--> Adding Diluted Sample to Mix") - protocol.comment("==============================================") - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A6" - Y = "A10" - p50.pick_up_tip() - p50.aspirate(SampleVol, dilution_plate_1[X].center(), rate=0.5) - p50.dispense(SampleVol, dilution_plate_1[Y].bottom(z=p50_offset_Temp), rate=0.5) - p50.default_speed = 2.5 - p50.move_to(dilution_plate_1[Y].center()) - protocol.delay(seconds=2) - p50.default_speed = 400 - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A7" - Y = "A11" - p50.pick_up_tip() - p50.aspirate(SampleVol, dilution_plate_1[X].center(), rate=0.5) - p50.dispense(SampleVol, dilution_plate_1[Y].bottom(z=p50_offset_Temp), rate=0.5) - p50.default_speed = 2.5 - p50.move_to(dilution_plate_1[Y].center()) - protocol.delay(seconds=2) - p50.default_speed = 400 - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A8" - Y = "A12" - p50.pick_up_tip() - p50.aspirate(SampleVol, dilution_plate_1[X].center(), rate=0.5) - p50.dispense(SampleVol, dilution_plate_1[Y].bottom(z=p50_offset_Temp), rate=0.5) - p50.default_speed = 2.5 - p50.move_to(dilution_plate_1[Y].center()) - protocol.delay(seconds=2) - p50.default_speed = 400 - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A6" - Y = "A10" - p50.pick_up_tip() - p50.aspirate(SampleVol, dilution_plate_2[X].center(), rate=0.5) - p50.dispense(SampleVol, dilution_plate_2[Y].bottom(z=p50_offset_Deck), rate=0.5) - p50.default_speed = 2.5 - p50.move_to(dilution_plate_2[Y].center()) - protocol.delay(seconds=2) - p50.default_speed = 400 - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A7" - Y = "A11" - p50.pick_up_tip() - p50.aspirate(SampleVol, dilution_plate_2[X].center(), rate=0.5) - p50.dispense(SampleVol, dilution_plate_2[Y].bottom(z=p50_offset_Deck), rate=0.5) - p50.default_speed = 2.5 - p50.move_to(dilution_plate_2[Y].center()) - protocol.delay(seconds=2) - p50.default_speed = 400 - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A8" - Y = "A12" - p50.pick_up_tip() - p50.aspirate(SampleVol, dilution_plate_2[X].center(), rate=0.5) - p50.dispense(SampleVol, dilution_plate_2[Y].bottom(z=p50_offset_Deck), rate=0.5) - p50.default_speed = 2.5 - p50.move_to(dilution_plate_2[Y].center()) - protocol.delay(seconds=2) - p50.default_speed = 400 - p50.drop_tip() if DRYRUN == "NO" else p50.return_tip() - - if STEP_DISPENSE == 1: - if FORMAT == "96": - protocol.comment("==============================================") - protocol.comment("--> Dispensing 96 well") - protocol.comment("==============================================") - X = "A9" - Y1 = "A1" - Y2 = "A2" - Y3 = "A3" - p1000.pick_up_tip() - p1000.aspirate(60, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y1].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y2].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y3].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A10" - Y1 = "A4" - Y2 = "A5" - Y3 = "A6" - p1000.pick_up_tip() - p1000.aspirate(60, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y1].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y2].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y3].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A11" - Y1 = "A7" - Y2 = "A8" - Y3 = "A9" - p1000.pick_up_tip() - p1000.aspirate(60, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y1].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y2].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y3].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A12" - Y1 = "A10" - Y2 = "A11" - Y3 = "A12" - p1000.pick_up_tip() - p1000.aspirate(60, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y1].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y2].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.dispense(20, qpcrplate_1[Y3].bottom(z=p1000_offset_Mag), rate=0.5) - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if FORMAT == "384": - p1000.reset_tipracks() - p50.reset_tipracks() - - protocol.comment("==============================================") - protocol.comment("--> Dispensing 384 well") - protocol.comment("==============================================") - X = "A9" - Y1 = "A1" - Y2 = "A2" - Y3 = "A3" - Y4 = "A4" - Y5 = "A5" - Y6 = "A6" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - p1000.mix(30, 58) - p1000.aspirate(62, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - protocol.delay(seconds=0.2) - p1000.move_to(qpcrplate_1[Y1].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y1].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y2].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y2].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y3].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y3].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.move_to(qpcrplate_1[Y4].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y4].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y5].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y5].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y6].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y6].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A10" - Y1 = "B1" - Y2 = "B2" - Y3 = "B3" - Y4 = "B4" - Y5 = "B5" - Y6 = "B6" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - p1000.mix(30, 58) - p1000.aspirate(62, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - protocol.delay(seconds=0.2) - p1000.move_to(qpcrplate_1[Y1].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y1].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y2].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y2].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y3].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y3].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.move_to(qpcrplate_1[Y4].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y4].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y5].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y5].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y6].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y6].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A11" - Y1 = "A7" - Y2 = "A8" - Y3 = "A9" - Y4 = "A10" - Y5 = "A11" - Y6 = "A12" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - p1000.mix(30, 58) - p1000.aspirate(62, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - protocol.delay(seconds=0.2) - p1000.move_to(qpcrplate_1[Y1].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y1].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y2].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y2].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y3].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y3].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.move_to(qpcrplate_1[Y4].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y4].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y5].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y5].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y6].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y6].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A12" - Y1 = "B7" - Y2 = "B8" - Y3 = "B9" - Y4 = "B10" - Y5 = "B11" - Y6 = "B12" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_1[X].bottom(z=p1000_offset_Temp)) - p1000.mix(30, 58) - p1000.aspirate(62, dilution_plate_1[X].bottom(z=p1000_offset_Temp), rate=0.25) - protocol.delay(seconds=0.2) - p1000.move_to(qpcrplate_1[Y1].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y1].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y2].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y2].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y3].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y3].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.move_to(qpcrplate_1[Y4].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y4].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y5].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y5].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_1[Y6].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_1[Y6].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - X = "A9" - Y1 = "A1" - Y2 = "A2" - Y3 = "A3" - Y4 = "A4" - Y5 = "A5" - Y6 = "A6" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_2[X].bottom(z=p1000_offset_Temp)) - p1000.mix(30, 58) - p1000.aspirate(62, dilution_plate_2[X].bottom(z=p1000_offset_Temp), rate=0.25) - protocol.delay(seconds=0.2) - p1000.move_to(qpcrplate_2[Y1].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y1].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y2].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y2].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y3].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y3].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.move_to(qpcrplate_2[Y4].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y4].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y5].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y5].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y6].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y6].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - if samplecolumns >= 1: # ----------------------------------------------------------------------------------------- - X = "A10" - Y1 = "B1" - Y2 = "B2" - Y3 = "B3" - Y4 = "B4" - Y5 = "B5" - Y6 = "B6" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_2[X].bottom(z=p1000_offset_Temp)) - p1000.mix(30, 58) - p1000.aspirate(62, dilution_plate_2[X].bottom(z=p1000_offset_Temp), rate=0.25) - protocol.delay(seconds=0.2) - p1000.move_to(qpcrplate_2[Y1].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y1].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y2].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y2].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y3].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y3].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.move_to(qpcrplate_2[Y4].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y4].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y5].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y5].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y6].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y6].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - if samplecolumns >= 2: # ----------------------------------------------------------------------------------------- - X = "A11" - Y1 = "A7" - Y2 = "A8" - Y3 = "A9" - Y4 = "A10" - Y5 = "A11" - Y6 = "A12" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_2[X].bottom(z=p1000_offset_Temp)) - p1000.mix(30, 58) - p1000.aspirate(62, dilution_plate_2[X].bottom(z=p1000_offset_Temp), rate=0.25) - protocol.delay(seconds=0.2) - p1000.move_to(qpcrplate_2[Y1].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y1].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y2].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y2].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y3].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y3].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.move_to(qpcrplate_2[Y4].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y4].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y5].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y5].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y6].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y6].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - if samplecolumns >= 3: # ----------------------------------------------------------------------------------------- - X = "A12" - Y1 = "B7" - Y2 = "B8" - Y3 = "B9" - Y4 = "B10" - Y5 = "B11" - Y6 = "B12" - p1000.pick_up_tip() - p1000.move_to(dilution_plate_2[X].bottom(z=p1000_offset_Temp)) - p1000.mix(30, 58) - p1000.aspirate(62, dilution_plate_2[X].bottom(z=p1000_offset_Temp), rate=0.25) - protocol.delay(seconds=0.2) - p1000.move_to(qpcrplate_2[Y1].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y1].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y2].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y2].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y3].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y3].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.move_to(qpcrplate_2[Y4].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y4].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y5].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y5].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - p1000.move_to(qpcrplate_2[Y6].top(z=1.0)) - protocol.delay(seconds=0.2) - p1000.default_speed = 2.5 - p1000.dispense(10, qpcrplate_2[Y6].bottom(z=1.75), rate=0.25) - protocol.delay(seconds=0.2) - p1000.default_speed = 400 - - p1000.drop_tip() if DRYRUN == "NO" else p1000.return_tip() - - p1000.reset_tipracks() - p50.reset_tipracks() - repeat += 1 diff --git a/app-testing/files/protocols/pl_96_ch_demo_rtp.py b/app-testing/files/protocols/pl_96_ch_demo_rtp.py new file mode 100644 index 00000000000..32d3ca60b45 --- /dev/null +++ b/app-testing/files/protocols/pl_96_ch_demo_rtp.py @@ -0,0 +1,222 @@ +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "RTP Demo DNA Extration with 96-channel pipette", + "author": "Anurag Kanase ", + "description": "DNA extraction Flex with 96-ch and Run Time Parameters", +} + +# requirements +requirements = {"robotType": "Flex", "apiLevel": "2.18"} + + +def add_parameters(parameters): + get_choices_plate = lambda: [{"display_name": str(i), "value": i} for i in range(1, 3)] + get_choices_batd = lambda: [{"display_name": str(i), "value": i} for i in [True, False]] + get_choices_mixes = lambda: [{"display_name": str(i), "value": i} for i in range(3, 6)] + get_choices_bind = lambda: [{"display_name": str(i) + " seconds", "value": i} for i in range(10, 60, 10)] + + parameters.add_int( + variable_name="plate_count", + display_name="Plate count", + description="Number of input plates per run (max. 2 plates)", + default=2, + choices=get_choices_plate(), + ) + + parameters.add_int( + variable_name="mixes", + display_name="Bead mixes", + description="Number of mixes to resuspend the beads", + default=3, + choices=get_choices_mixes(), + ) + + parameters.add_int( + variable_name="bind", + display_name="Bead binding time (s)", + description="Duration of sample plate placement on magnetic block", + default=10, + choices=get_choices_bind(), + ) + + parameters.add_bool( + variable_name="batd", + display_name="Special mix", + description="Do you want to Aspirate beads at bottom and dispense at top of well?", + default=True, + ) + + +# protocol run function +def run(protocol: protocol_api.ProtocolContext): + + plate_count = protocol.params.plate_count + batd = protocol.params.batd + mixes = protocol.params.mixes + bind_time = protocol.params.bind + + # labware + + tip1000 = [ + protocol.load_labware("opentrons_flex_96_filtertiprack_1000ul", location=slot, adapter="opentrons_flex_96_tiprack_adapter") + for slot in ["A1", "A2", "B1"] + ] + + trash = protocol.load_trash_bin(location="A3") + + dwp = [ + protocol.load_labware("nest_96_wellplate_2ml_deep", location=slot, label=f"Sample Plate {no}") + for slot, no in zip(["C2", "C3"][:plate_count], [1, 2][:plate_count]) + ] + + pcr = [ + protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", location=slot, label=f"Elution Plate {no}") + for slot, no in zip(["B2", "B3"][:plate_count], [1, 2][:plate_count]) + ] + + wash = protocol.load_labware("nest_1_reservoir_195ml", location="C1", label=f"Wash Buffer") + + elution = protocol.load_labware("nest_1_reservoir_195ml", location="D1", label=f"Elution buffer") + + beads = protocol.load_labware("nest_12_reservoir_15ml", "D2", label="Binding buffer + Beads") + + magblock = protocol.load_module("magneticBlockV1", location="D3") + # pipettes + pip = protocol.load_instrument("flex_96channel_1000", mount="left") + + protocol.comment("-------- Resuspending Beads --------") + src = beads["A1"] + dest = beads["A1"] + pip.pick_up_tip(tip1000[2]["A1"]) + for i in range(mixes): + pip.aspirate(1000, src.bottom(2)) + if batd: + pip.dispense(1000, dest.top()) + else: + pip.dispense(1000, dest.bottom(2)) + + protocol.comment("------- Binding Bead Transfer ------") + + for dest in dwp: + src = beads["A1"] + pip.aspirate(100, src.bottom(2)) + pip.dispense(100, dest["A1"].top(-4)) + + pip.return_tip() + + protocol.comment("------- Sample Bead Binding ------") + + for i in range(plate_count): + pip.pick_up_tip(tip1000[i]["A1"]) + for j in range(mixes): + src = dwp[i]["A1"] + dest = dwp[i]["A1"] + pip.aspirate(1000, src.bottom(2)) + if batd: + pip.dispense(1000, dest.top()) + else: + pip.dispense(1000, dest.bottom(2)) + pip.return_tip() + protocol.comment(f"Bead binding on {dwp[i].name}") + protocol.move_labware(dwp[i], magblock, use_gripper=True) + pip.pick_up_tip(tip1000[i]["A1"]) + protocol.delay(bind_time / 5) + protocol.comment("----- Removing Supernatant -------") + pip.aspirate(500, dwp[i]["A1"].bottom(2)) + pip.dispense(500, trash) + pip.return_tip() + if i == 0: + protocol.move_labware(dwp[i], "C2", use_gripper=True) + else: + protocol.move_labware(dwp[i], "C3", use_gripper=True) + + protocol.comment("-------- Transferring Washbuffer --------") + src = wash["A1"] + pip.pick_up_tip(tip1000[2]["A1"]) + for dest in dwp: + src = wash["A1"] + pip.aspirate(100, src.bottom(2)) + pip.dispense(100, dest["A1"].top(-4)) + pip.return_tip() + + protocol.comment("------- Washing Beads ------") + + for i in range(plate_count): + pip.pick_up_tip(tip1000[i]["A1"]) + for j in range(mixes): + src = dwp[i]["A1"] + dest = dwp[i]["A1"] + pip.aspirate(1000, src.bottom(2)) + if batd: + pip.dispense(1000, dest.top()) + else: + pip.dispense(1000, dest.bottom(2)) + pip.return_tip() + protocol.comment(f"Bead washing on {dwp[i].name}") + protocol.move_labware(dwp[i], magblock, use_gripper=True) + pip.pick_up_tip(tip1000[i]["A1"]) + protocol.delay(bind_time / 5) + protocol.comment("----- Removing Supernatant -------") + pip.aspirate(500, dwp[i]["A1"].bottom(2)) + pip.dispense(500, trash) + pip.return_tip() + if i == 0: + protocol.move_labware(dwp[i], "C2", use_gripper=True) + else: + protocol.move_labware(dwp[i], "C3", use_gripper=True) + + protocol.comment("====== BEAD WASHING COMPLETE =========") + + protocol.comment("-------- Transferring Elution Buffer --------") + src = elution["A1"] + pip.pick_up_tip(tip1000[2]["A1"]) + for dest in dwp: + src = elution["A1"] + pip.aspirate(100, src.bottom(2)) + pip.dispense(100, dest["A1"].top(-4)) + pip.return_tip() + + protocol.comment("------- Eluting NA ------") + + for i in range(plate_count): + pip.pick_up_tip(tip1000[i]["A1"]) + for j in range(mixes): + src = dwp[i]["A1"] + dest = dwp[i]["A1"] + pip.aspirate(100, src.bottom(2)) + if batd: + pip.dispense(100, dest.top()) + else: + pip.dispense(100, dest.bottom(2)) + pip.return_tip() + protocol.comment(f"Eluting on {dwp[i].name}") + protocol.move_labware(dwp[i], magblock, use_gripper=True) + pip.pick_up_tip(tip1000[i]["A1"]) + protocol.delay(bind_time / 5) + protocol.comment("----- Eluting 40 uL DNA -------") + pip.aspirate(40, dwp[i]["A1"].bottom(2)) + pip.dispense(40, pcr[i]["A1"].bottom(2)) + pip.return_tip() + if i == 0: + protocol.move_labware(dwp[i], "C2", use_gripper=True) + else: + protocol.move_labware(dwp[i], "C3", use_gripper=True) + + protocol.comment("====== DNA Extraction Completed =========") + + def add_liquid(name, color, well, volume): + liquid = protocol.define_liquid(name=name, description="generic", display_color=color) + well.load_liquid(liquid=liquid, volume=volume) + return liquid, well + + for dw in dwp: + for well in dw.wells(): + add_liquid("Sample", "#E8C547", well, 200) + + for well in beads.wells(): + add_liquid("Beads", "#1F9D8A", well, 2000) + + add_liquid("Elution Buffer", "#B28FCE", elution["A1"], 20000) + add_liquid("Wash Buffer", "#5D2E8C", wash["A1"], 20000) diff --git a/app-testing/files/protocols/pl_96_ch_demo_rtp_with_hs.py b/app-testing/files/protocols/pl_96_ch_demo_rtp_with_hs.py new file mode 100644 index 00000000000..bd4af743d32 --- /dev/null +++ b/app-testing/files/protocols/pl_96_ch_demo_rtp_with_hs.py @@ -0,0 +1,276 @@ +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "RTP Demo DNA Extration with 96-channel pipette", + "author": "Anurag Kanase ", + "description": "DNA extraction Flex with 96-ch and Run Time Parameters", +} + +# requirements +requirements = {"robotType": "Flex", "apiLevel": "2.18"} + + +def add_parameters(parameters): + get_choices_plate = lambda: [{"display_name": str(i), "value": i} for i in range(1, 3)] + get_choices_batd = lambda: [{"display_name": str(i), "value": i} for i in [True, False]] + get_choices_mixes = lambda: [{"display_name": str(i), "value": i} for i in range(3, 6)] + get_choices_bind = lambda: [{"display_name": str(i) + " seconds", "value": i} for i in range(10, 60, 10)] + + parameters.add_int( + variable_name="plate_count", + display_name="Plate count", + description="Number of input plates per run (max. 2 plates)", + default=2, + choices=get_choices_plate(), + ) + + parameters.add_int( + variable_name="mixes", + display_name="Bead mixes", + description="Number of mixes to resuspend the beads", + default=3, + choices=get_choices_mixes(), + ) + + parameters.add_int( + variable_name="bind", + display_name="Bead binding time (s)", + description="Duration of sample plate placement on magnetic block", + default=10, + choices=get_choices_bind(), + ) + + parameters.add_bool( + variable_name="batd", + display_name="Special mix", + description="Do you want to Aspirate beads at bottom and dispense at top of well?", + default=True, + ) + parameters.add_bool( + variable_name="hs_mix", + display_name="Mix with Heater Shaker", + description="Use heater-shaker module for sample + bead mixing", + default=True, + ) + + +# protocol run function +def run(protocol: protocol_api.ProtocolContext): + + plate_count = protocol.params.plate_count + batd = protocol.params.batd + mixes = protocol.params.mixes + bind_time = protocol.params.bind + hs_mixes = protocol.params.hs_mix + + # modules + hs = protocol.load_module(module_name="heaterShakerModuleV1", location="B1") + hs_adapt = hs.load_adapter("opentrons_96_deep_well_adapter") + hs.close_labware_latch() + + def shake(time): + hs.close_labware_latch() + hs.set_and_wait_for_shake_speed(1000) + protocol.delay(time) + hs.deactivate_shaker() + + # labware + + # tip1000 = [protocol.load_labware('opentrons_flex_96_filtertiprack_1000ul', + # location=slot, adapter="opentrons_flex_96_tiprack_adapter") for slot in ["A1", "A2", "A3"]] + tip1k = protocol.load_labware("opentrons_flex_96_filtertiprack_1000ul", location="A1", adapter="opentrons_flex_96_tiprack_adapter") + tip200 = protocol.load_labware("opentrons_flex_96_filtertiprack_200ul", location="A2", adapter="opentrons_flex_96_tiprack_adapter") + tip50 = protocol.load_labware("opentrons_flex_96_filtertiprack_50ul", location="A4") + tip50_adapt = protocol.load_adapter("opentrons_flex_96_tiprack_adapter", location="A3") + tip1000 = [tip1k, tip200, tip50] + trash = protocol.load_trash_bin(location="D1") + + dwp = [ + protocol.load_labware("nest_96_wellplate_2ml_deep", location=slot, label=f"Sample Plate {no}") + for slot, no in zip(["B2", "B3"][:plate_count], [1, 2][:plate_count]) + ] + + pcr = [ + protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", location=slot, label=f"Elution Plate {no}") + for slot, no in zip(["C4", "D4"][:plate_count], [1, 2][:plate_count]) + ] + + wash = protocol.load_labware("nest_1_reservoir_195ml", location="C2", label=f"Wash Buffer") + + elution = protocol.load_labware("nest_1_reservoir_195ml", location="C3", label=f"Elution buffer") + + beads = protocol.load_labware("nest_12_reservoir_15ml", "C1", label="Binding buffer + Beads") + + magblock = protocol.load_module("magneticBlockV1", location="D3") + # pipettes + pip = protocol.load_instrument("flex_96channel_1000", mount="left") + + protocol.comment("-------- Resuspending Beads --------") + src = beads["A1"] + dest = beads["A1"] + pip.pick_up_tip(tip1000[0]["A1"]) + for i in range(mixes): + pip.aspirate(200, src.bottom(2)) + if batd: + pip.dispense(200, dest.top()) + else: + pip.dispense(200, dest.bottom(2)) + + protocol.comment("------- Binding Bead Transfer ------") + + for dest in dwp: + src = beads["A1"] + pip.aspirate(100, src.bottom(2)) + pip.dispense(100, dest["A1"].top(-4)) + + pip.return_tip() + + protocol.comment("------- Sample + Bead Binding ------") + + for i in range(plate_count): + if hs_mixes: + hs.open_labware_latch() + protocol.move_labware(dwp[i], hs_adapt, use_gripper=True) + protocol.comment(f"Bead binding on {dwp[i].name}") + shake(10) + hs.open_labware_latch() + protocol.move_labware(dwp[i], magblock, use_gripper=True) + hs.close_labware_latch() + else: + pip.pick_up_tip(tip1000[i]["A1"]) + for j in range(mixes): + src = dwp[i]["A1"] + dest = dwp[i]["A1"] + pip.aspirate(1000, src.bottom(2)) + if batd: + pip.dispense(1000, dest.top()) + else: + pip.dispense(1000, dest.bottom(2)) + pip.return_tip() + protocol.comment(f"Bead binding on {dwp[i].name}") + protocol.move_labware(dwp[i], magblock, use_gripper=True) + pip.pick_up_tip(tip1000[i]["A1"]) + protocol.delay(bind_time / 5) + protocol.comment("----- Removing Supernatant -------") + pip.aspirate(200, dwp[i]["A1"].bottom(2)) + pip.dispense(200, trash) + pip.return_tip() + if i == 0: + protocol.move_labware(dwp[i], "B2", use_gripper=True) + else: + protocol.move_labware(dwp[i], "B3", use_gripper=True) + + protocol.comment("-------- Transferring Washbuffer --------") + src = wash["A1"] + pip.pick_up_tip(tip1000[0]["A1"]) + for dest in dwp: + src = wash["A1"] + pip.aspirate(100, src.bottom(2)) + pip.dispense(100, dest["A1"].top(-4)) + pip.return_tip() + + protocol.comment("------- Washing Beads ------") + + for i in range(plate_count): + if hs_mixes: + hs.open_labware_latch() + protocol.move_labware(dwp[i], hs_adapt, use_gripper=True) + protocol.comment(f"Bead binding on {dwp[i].name}") + shake(10) + hs.open_labware_latch() + protocol.move_labware(dwp[i], magblock, use_gripper=True) + hs.close_labware_latch() + else: + pip.pick_up_tip(tip1000[i]["A1"]) + for j in range(mixes): + src = dwp[i]["A1"] + dest = dwp[i]["A1"] + pip.aspirate(200, src.bottom(2)) + if batd: + pip.dispense(200, dest.top()) + else: + pip.dispense(200, dest.bottom(2)) + pip.return_tip() + protocol.comment(f"Bead washing on {dwp[i].name}") + protocol.move_labware(dwp[i], magblock, use_gripper=True) + pip.pick_up_tip(tip1000[i]["A1"]) + protocol.delay(bind_time / 5) + protocol.comment("----- Removing Supernatant -------") + pip.aspirate(200, dwp[i]["A1"].bottom(2)) + pip.dispense(200, trash) + pip.return_tip() + if i == 0: + protocol.move_labware(dwp[i], "B2", use_gripper=True) + else: + protocol.move_labware(dwp[i], "B3", use_gripper=True) + + protocol.comment("-------- BEAD WASHING COMPLETE -------") + + protocol.comment("==========Preparing for ELUTION ============") + protocol.move_labware(beads, "B4", use_gripper="True") + protocol.move_labware(pcr[0], "C1", use_gripper="True") + protocol.move_labware(wash, "C4", use_gripper="True") + protocol.move_labware(pcr[1], "C2", use_gripper="True") + protocol.move_labware(tip50, tip50_adapt, use_gripper="True") + + protocol.comment("-------- Transferring Elution Buffer --------") + src = elution["A1"] + pip.pick_up_tip(tip1000[2]["A1"]) + for dest in dwp: + src = elution["A1"] + pip.aspirate(50, src.bottom(2)) + pip.dispense(50, dest["A1"].top(-4)) + pip.return_tip() + + protocol.comment("------- Eluting NA ------") + + for i in range(plate_count): + if hs_mixes: + hs.open_labware_latch() + protocol.move_labware(dwp[i], hs_adapt, use_gripper=True) + protocol.comment(f"Bead binding on {dwp[i].name}") + shake(10) + hs.open_labware_latch() + protocol.move_labware(dwp[i], magblock, use_gripper=True) + hs.close_labware_latch() + else: + pip.pick_up_tip(tip1000[i]["A1"]) + for j in range(mixes): + src = dwp[i]["A1"] + dest = dwp[i]["A1"] + pip.aspirate(100, src.bottom(2)) + if batd: + pip.dispense(100, dest.top()) + else: + pip.dispense(100, dest.bottom(2)) + pip.return_tip() + protocol.comment(f"Eluting on {dwp[i].name}") + protocol.move_labware(dwp[i], magblock, use_gripper=True) + pip.pick_up_tip(tip1000[2]["A1"]) + protocol.delay(bind_time / 5) + protocol.comment("----- Eluting 40 uL DNA -------") + pip.aspirate(40, dwp[i]["A1"].bottom(2)) + pip.dispense(40, pcr[i]["A1"].bottom(2)) + pip.return_tip() + if i == 0: + protocol.move_labware(dwp[i], "B2", use_gripper=True) + else: + protocol.move_labware(dwp[i], "B3", use_gripper=True) + + protocol.comment("====== DNA Extraction Completed =========") + + def add_liquid(name, color, well, volume): + liquid = protocol.define_liquid(name=name, description="generic", display_color=color) + well.load_liquid(liquid=liquid, volume=volume) + return liquid, well + + for dw in dwp: + for well in dw.wells(): + add_liquid("Sample", "#E8C547", well, 200) + + for well in beads.wells(): + add_liquid("Beads", "#1F9D8A", well, 2000) + + add_liquid("Elution Buffer", "#B28FCE", elution["A1"], 20000) + add_liquid("Wash Buffer", "#5D2E8C", wash["A1"], 20000) diff --git a/app-testing/files/protocols/pl_AMPure_XP_48x_v8.py b/app-testing/files/protocols/pl_AMPure_XP_48x_v8.py new file mode 100644 index 00000000000..164a7038671 --- /dev/null +++ b/app-testing/files/protocols/pl_AMPure_XP_48x_v8.py @@ -0,0 +1,1497 @@ +from opentrons import protocol_api +from opentrons import types +import math + +metadata = { + "protocolName": "AMPure XP 48x v8", + "author": "Opentrons ", + "source": "Protocol Library", +} +requirements = { + "robotType": "Flex", + "apiLevel": "2.18", +} + + +def add_parameters(parameters): + # ======================== RUNTIME PARAMETERS ======================== + parameters.add_bool(display_name="Dry Run", variable_name="DRYRUN", default=False, description="Whether to perform a dry run or not.") + parameters.add_int( + display_name="Sample Column count", + variable_name="COLUMNS", + default=3, + minimum=1, + maximum=6, + description="How many sample columns to process.", + ) + parameters.add_float( + display_name="Bead ratio (x)", + variable_name="BEADRATIO", + default=1.8, + minimum=0.5, + maximum=2, + description="Bead Ratio relative to input volume in " "x" ".", + ) + parameters.add_int( + display_name="Input Volume (µl)", variable_name="INPUTVOLUME", default=50, minimum=10, maximum=100, description="Input volume." + ) + parameters.add_int( + display_name="Resuspension Volume (µl)", + variable_name="RESUSPENSION", + default=20, + minimum=10, + maximum=100, + description="Resuspension volume.", + ) + parameters.add_bool( + display_name="Final Samples onto a New Plate", + variable_name="NEW_PLATE", + default=False, + description="Transfer the final samples to a new plate, will default to True if more than 6 samples", + ) + parameters.add_str( + display_name="Single / Double Size Selection", + variable_name="DOUBLEDSIDED", + default="Single Sided", + description="Single (Left) or double (Left+Right) Size Selection; Right Cut is 0.3x", + choices=[{"display_name": "Single Sided", "value": "Single Sided"}, {"display_name": "Double Sided", "value": "Double Sided"}], + ) + parameters.add_bool( + display_name="Mix with Heatershaker", + variable_name="TIP_MIX", + default=True, + description="Is the Heatershaker Module present and used for mixing", + ) + parameters.add_int( + display_name="Heatershaker Time (Minutes)", + variable_name="SHAKE_TIME", + default=30, + minimum=10, + maximum=100, + description="Shaking Time if using heatershaker.", + ) + parameters.add_int( + display_name="Heatershaker RPM", + variable_name="SHAKE_RPM", + default=1200, + minimum=10, + maximum=2000, + description="Shaking RPM if using heatershaker.", + ) + parameters.add_int( + display_name="EtOH Drying Time Minutes", + variable_name="EtOH_TIME", + default=2, + minimum=0, + maximum=100, + description="EtOH Drying Time in Minutes.", + ) + parameters.add_bool( + display_name="Thermocycler Module Present", + variable_name="ONDECK_THERMO", + default=True, + description="Is the Thermocycler Module present (but not used)", + ) + parameters.add_bool( + display_name="Temp Module Present", + variable_name="ONDECK_TEMP", + default=True, + description="Is the Temperature Module present (but not used)", + ) + parameters.add_str( + display_name="Trash Position", + variable_name="TRASH_POSITION", + default="CHUTE", + description="Trash Position", + choices=[{"display_name": "Trash Chute in D3", "value": "CHUTE"}, {"display_name": "Trash Bin in A3", "value": "BIN"}], + ) + parameters.add_str( + display_name="Tip Setting", + variable_name="TIP_SETTING", + default="Single Tip Use", + description="Tip Settings", + choices=[ + {"display_name": "Reusing Tips", "value": "Reusing Tips"}, + {"display_name": "Single Tip Use", "value": "Single Tip Use"}, + {"display_name": "None", "value": "None"}, + ], + ) + + +def run(protocol: protocol_api.ProtocolContext): + # ======================== DOWNLOADED PARAMETERS ======================== + global USE_GRIPPER # T/F Whether or not Using the Gripper + global STP_50_TIPS # T/F Whether or not there are p50 Single Tip Pickups + global STP_200_TIPS # T/F Whether or not there are p200 Single Tip Pickups + global REUSE_ANY_50_TIPS # T/F Whether or not Reusing any p50 + global REUSE_ANY_200_TIPS # T/F Whether or not Reusing any p200 + global TIP_TRASH # T/F whether or not the Tips are Returned + global COLUMNS # Number of Columns of Samples + global PLATE_STACKED # Number of Plates Stacked in Stacked Position + global p50_TIPS # Number of p50 tips currently available + global p200_TIPS # Number of p200 tips currently available + global p50_RACK_COUNT # Number of current total p50 racks + global p200_RACK_COUNT # Number of current total p200 racks + global tiprack_200_STP # Tiprack for p200 Single Tip Pickup + global tiprack_200_STR # Tiprack for p200 Single Tip Return + global tiprack_50_STP # Tiprack for p50 Single Tip Pickup + global tiprack_50_STR # Tiprack for p50 Single Tip Return + global tiprack_50_R # Tiprack for p50 Reuse + global tiprack_200_R1 # Tiprack for p200 Reuse #1 + global tiprack_200_R2 # Tiprack for p200 Reuse #2 + global WASTEVOL # Number - Total volume of Discarded Liquid Waste + global ETOHVOL # Number - Total volume of Available EtOH + + DRYRUN = protocol.params.DRYRUN + COLUMNS = protocol.params.COLUMNS + BEADRATIO = protocol.params.BEADRATIO + BEADRATIO_2 = 0.3 + INPUTVOLUME = protocol.params.INPUTVOLUME + RESUSPENSION = protocol.params.RESUSPENSION + SHAKE_TIME = protocol.params.SHAKE_TIME + SHAKE_RPM = protocol.params.SHAKE_RPM + EtOH_TIME = protocol.params.EtOH_TIME + NEW_PLATE = protocol.params.NEW_PLATE + DOUBLEDSIDED = protocol.params.DOUBLEDSIDED + TIP_MIX = protocol.params.TIP_MIX + ONDECK_THERMO = protocol.params.ONDECK_THERMO + ONDECK_TEMP = protocol.params.ONDECK_TEMP + TRASH_POSITION = protocol.params.TRASH_POSITION + TIP_SETTING = protocol.params.TIP_SETTING + + # ================================================================================================= + # ====================================== ADVANCED PARAMETERS ====================================== + # ================================================================================================= + # -------PROTOCOL STEP------- + STEP_CLEANUP = ( + True # This is a example Step in the protocol, example would be a Cleanup Step after library prep that can be optionally excluded. + ) + # --------------------------- + TIP_TRASH = True # True = Used tips go in Trash, False = Used tips go back into rack + DEACTIVATE_TEMP = True # Whether or not to deactivate the heating and cooling modules after a run + USE_GRIPPER = True # Using the Gripper + CUSTOM_OFFSETS = False # Manually enter offset position settings + RES_TYPE_96x = False # Type of Reservoir, if reusing tips or omitting rows, set True to use a 96x2ml deepwell + ETOH_1_AirMultiDis = False # When adding EtOH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + RSB_1_AirMultiDis = False # When adding RSB to multiple columns, dispense above the wells and reuse tips (Tip Saving) + COLUMN_SET_BATCH = False # Whether or not remove supernatant and add RSB in batches, to prevent uneven drying in high sample count runs + REUSE_50_TIPS_RSB = False # Reusing p50 tips + REUSE_200_TIPS_WASH = False # Reusing p200 tips + REUSE_200_TIPS_ETOH = False # Reusing p200 tips + STP_200_TIPS = False # Single Tip Pickup p200 tips + STP_50_TIPS = False # Single tip Pickup p50 tips + REPORT = True # Whether or not to include Extra Comments for Debugging + LABEL = True # Whether or not to include Liquid Labeling + + # ============================== SETTINGS =============================== + if TRASH_POSITION == "BIN": + SWAPOFFDECK = True # Setting to Swap empty Tipracks to empty positions instead of dumping them + if TRASH_POSITION == "CHUTE": + SWAPOFFDECK = False # Setting to Swap empty Tipracks to empty positions instead of dumping them + if TIP_MIX == True: + ONDECK_HEATERSHAKER = True # On Deck Heater Shaker + if TIP_MIX == False: + ONDECK_HEATERSHAKER = False # On Deck Heater Shaker + if DRYRUN == True: + TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack + DEACTIVATE_TEMP = True # Whether or not to deactivate the heating and cooling modules after a run + REPORT = True # Whether or not to include Extra Comments for Debugging + if TIP_SETTING == "Reusing Tips": + RES_TYPE_96x = True # Type of Reservoir, if reusing tips or omitting rows, set True to use a 96x2ml deepwell + ETOH_1_AirMultiDis = True # When adding EtOH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + REUSE_50_TIPS_RSB = True # Reusing p50 tips + REUSE_200_TIPS_WASH = True # Reusing p200 tips + REUSE_200_TIPS_ETOH = True # Reusing p200 tips + if TIP_SETTING == "Single Tip Use": + RES_TYPE_96x = False # Type of Reservoir, if reusing tips or omitting rows, set True to use a 96x2ml deepwell + ETOH_1_AirMultiDis = False # When adding EtOH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + REUSE_50_TIPS_RSB = False # Reusing p50 tips + REUSE_200_TIPS_WASH = False # Reusing p200 tips + REUSE_200_TIPS_ETOH = False # Reusing p200 tips + + # ======================== BACKGROUND PARAMETERS ======================== + p50_TIPS = 0 # Number of p50 tips currently available + p200_TIPS = 0 # Number of p50 tips currently available + RESETCOUNT = 0 # Number of times the protocol was paused to reset tips + p50_RACK_COUNT = 0 # Number of current total p50 racks + p200_RACK_COUNT = 0 # Number of current total p200 racks + WASTEVOL = 0 # Number - Total volume of Discarded Liquid Waste + ETOHVOL = 0 # Number - Total volume of Available EtOH + PLATE_STACKED = 0 # Number of Plates Stacked in Stacked Position + REUSE_50_TIPS_COUNT = 0 + REUSE_ANY_50_TIPS = False + if REUSE_50_TIPS_RSB == True: + REUSE_ANY_50_TIPS = True + REUSE_50_TIPS_COUNT += COLUMNS + REUSE_200_TIPS_COUNT = 0 + REUSE_ANY_200_TIPS = False + if REUSE_200_TIPS_WASH == True: + REUSE_ANY_200_TIPS = True + REUSE_200_TIPS_COUNT += COLUMNS + if REUSE_200_TIPS_ETOH == True: + REUSE_ANY_200_TIPS = True + REUSE_200_TIPS_COUNT += COLUMNS + + # =============================== PIPETTE =============================== + p1000 = protocol.load_instrument("flex_8channel_1000", "left") + p50 = protocol.load_instrument("flex_8channel_50", "right") + p1000_flow_rate_aspirate_default = 200 + p1000_flow_rate_dispense_default = 200 + p1000_flow_rate_blow_out_default = 400 + p50_flow_rate_aspirate_default = 50 + p50_flow_rate_dispense_default = 50 + p50_flow_rate_blow_out_default = 100 + + # ================================ LISTS ================================ + p50_RACKS_PIPET = [] # Pipette List + p200_RACKS_PIPET = [] # Pipette List + AVAILABLE_POS_ONDECK = [] # List of Available Positions ON DECK + AVAILABLE_POS_OFFDECK = [] # List of Available Positions OFF DECK + RACKS_TO_DUMP = [] # List of Emptied Racks ON DECK + p50_RACKS_ONDECK = [] # List of P50 Racks ON DECK + p50_RACKS_OFFDECK = [] # List of P50 Racks OFF DECK + p50_RACKS_DROPPED = [] # List of P50 Racks DROPPED + p200_RACKS_ONDECK = [] # List of P200 Racks ON DECK + p200_RACKS_OFFDECK = [] # List of P200 Racks OFF DECK + p200_RACKS_DROPPED = [] # List of P200 Racks DROPPED + SWAPSPOT = [] # List of Next Available Position for SWAP + REUSE_50_TIPS = [] # List of Next Available Position for SWAP + p50_INITIALTIPS = [] # List of Next Available Position for SWAP + REUSE_200_TIPS_1 = [] # List of Next Available Position for SWAP + REUSE_200_TIPS_2 = [] # List of Next Available Position for SWAP + p200_INITIALTIPS = [] # List of Next Available Position for SWAP + + def DefinePosition(tiptype, position, status): + # A Function that is called for potential tip Rack Position. Rather than defining tipracks at the beginning this function is called for each potential tip rack position, values are passed + # to the function and the tip position is added to the appropriate list as, Single Tip Pickup (STP), Reusable Tips, of left as OPEN which can be filled with p50 or p200 as needed. + global STP_50_TIPS + global STP_200_TIPS + global COLUMNS + global REUSE_ANY_50_TIPS + global REUSE_ANY_200_TIPS + global p50_RACK_COUNT + global p200_RACK_COUNT + global tiprack_200_STP + global tiprack_200_STR + global tiprack_50_R + global tiprack_200_R1 + global tiprack_200_R2 + if status == "OPEN": + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "STP_200" and STP_200_TIPS == True: + tiprack_200_STP = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_STP") + for i in range(1, 13): + STP_200_list_x4.append(tiprack_200_STP[f"E{i}"]) + STP_200_list_x4.append(tiprack_200_STP[f"A{i}"]) + rows = ["H", "G", "F", "E", "D", "C", "B", "A"] + columns = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] + for col in columns: + for row in rows: + STP_200_list_x1.append(tiprack_200_STP[row + col]) + if status == "STR_200" and STP_200_TIPS == True: + tiprack_200_STR = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_STR") + for i in range(1, 13): + STR_200_list.append(tiprack_200_STR[f"A{i}"]) + STR_200_list.append(tiprack_200_STR[f"E{i}"]) + if status == "STP_50" and STP_50_TIPS == True: + tiprack_50_STP = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_STP") + for i in range(1, 13): + STP_50_list_x4.append(tiprack_50_STP[f"E{i}"]) + STP_50_list_x4.append(tiprack_50_STP[f"A{i}"]) + rows = ["H", "G", "F", "E", "D", "C", "B", "A"] + columns = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] + for col in columns: + for row in rows: + STP_50_list_x1.append(tiprack_50_STP[row + col]) + if status == "STR_50" and STP_50_TIPS == True: + tiprack_50_STR = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_STR") + for i in range(1, 13): + STR_50_list.append(tiprack_50_STR[f"A{i}"]) + STR_50_list.append(tiprack_50_STR[f"E{i}"]) + if status == "REUSE_50_TIPS" and REUSE_ANY_50_TIPS == True: + p50_RACK_COUNT += 1 + tiprack_50_R = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_R") + protocol.comment(f"Adding tiprack_50_R") + for X in range(COLUMNS): + REUSE_50_TIPS.append(tiprack_50_R.wells_by_name().get(f"A{X+1}", None)) + for X in range(COLUMNS, 12): + p50_INITIALTIPS.append(tiprack_50_R.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_50_TIPS" and REUSE_ANY_50_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "REUSE_200_1TIPS" and REUSE_ANY_200_TIPS == True: + p200_RACK_COUNT += 1 + tiprack_200_R1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_R1") + protocol.comment(f"Adding tiprack_200_R1") + for X in range(COLUMNS): + REUSE_200_TIPS_1.append(tiprack_200_R1.wells_by_name().get(f"A{X+1}", None)) + for X in range(COLUMNS, 12): + p200_INITIALTIPS.append(tiprack_200_R1.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_200_1TIPS" and REUSE_ANY_200_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "REUSE_200_2TIPS" and REUSE_ANY_200_TIPS == True: + p200_RACK_COUNT += 1 + tiprack_200_R2 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_R2") + protocol.comment(f"Adding tiprack_200_R2") + for X in range(COLUMNS * 2): + REUSE_200_TIPS_2.append(tiprack_200_R2.wells_by_name().get(f"A{X+1}", None)) + for X in range(COLUMNS * 2, 12): + p200_INITIALTIPS.append(tiprack_200_R2.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_200_2TIPS" and REUSE_ANY_200_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + + def TipCheck(tiptype, tipuse, rep, tipset): + # A Function that is called replacing the Tip Pickup Step. Values are passed to the function such as tip type, reuse, and which rep (loop number) it is on. + # 1). Checks if there are 0 spaces available, if so, it clears the off deck positions allowing it to be refilled + # 2). Then, if its not Single Tip Pickup (which has its own separate list), if there are no positions available, and not a Reusing step, it adds a rack to either the On or Off Deck + # 3). If it is an Off Deck Position, it automatically starts the TipRackSwap Function, removing the next in line empry rack and swapping in the newly added Off Deck rack + global p50_TIPS + global p200_TIPS + global p50_RACK_COUNT + global p200_RACK_COUNT + global RESETCOUNT + global TIPDONEMODE + SOFTPAUSE = False + if len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) == 0: + for loop, X in enumerate(OFFDECK_LIST): + del protocol.deck[X] + if SOFTPAUSE == False: + protocol.pause("CLEARING OFFDECK POSITIONS") + protocol.comment("CLEARING OFFDECK POSITIONS") + AVAILABLE_POS_OFFDECK.append(X) + if tiptype == 50 and tipuse != "STP": + if p50_TIPS == 0 and len(p50_INITIALTIPS) == 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + if len(p50_RACKS_ONDECK) == 0: + protocol.comment("FIRST RACK") + else: + protocol.comment("IS THERE AN EMPTY RACK?") + RACKS_TO_DUMP.append(p50_RACKS_ONDECK[0]) + p50_RACKS_ONDECK.pop(0) + p50_RACK_COUNT += 1 + p50_TIPS += 12 + protocol.comment(f"Adding tiprack_50_{p50_RACK_COUNT}") + if len(AVAILABLE_POS_ONDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_50ul", AVAILABLE_POS_ONDECK[0], f"tiprack_50_{p50_RACK_COUNT}" + ) + AVAILABLE_POS_ONDECK.pop(0) + p50_RACKS_ONDECK.append(addtiprack) + p50_RACKS_PIPET.append(addtiprack) + p50.tip_racks = p50_RACKS_PIPET + elif len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_50ul", AVAILABLE_POS_OFFDECK[0], f"tiprack_50_{p50_RACK_COUNT}" + ) + AVAILABLE_POS_OFFDECK.pop(0) + p50_RACKS_OFFDECK.append(addtiprack) + p50_RACKS_PIPET.append(addtiprack) + p50.tip_racks = p50_RACKS_PIPET + TipRackSwap(50) + if p50_TIPS == 0 and len(p50_INITIALTIPS) > 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + p50.pick_up_tip(p50_INITIALTIPS[0]) + p50_INITIALTIPS.pop(0) + if p50_TIPS > 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + if len(p50_INITIALTIPS) > 0: + p50.pick_up_tip(p50_INITIALTIPS[0]) + p50_INITIALTIPS.pop(0) + else: + p50_TIPS -= 1 + p50.pick_up_tip() + if tiptype == 200 and tipuse != "STP": + if p200_TIPS == 0 and len(p200_INITIALTIPS) == 0: + if tipuse == "REUSE" and tipset == "WASH" and REUSE_200_TIPS_WASH == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + elif tipuse == "REUSE" and tipset == "ETOH" and REUSE_200_TIPS_ETOH == True: + p1000.pick_up_tip(REUSE_200_TIPS_2[rep]) + else: + if len(p200_RACKS_ONDECK) == 0: + protocol.comment("FIRST RACK") + else: + protocol.comment("IS THERE AN EMPTY RACK?") + RACKS_TO_DUMP.append(p200_RACKS_ONDECK[0]) + p200_RACKS_ONDECK.pop(0) + p200_RACK_COUNT += 1 + p200_TIPS += 12 + protocol.comment(f"Adding tiprack_200_{p200_RACK_COUNT}") + if len(AVAILABLE_POS_ONDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_200ul", AVAILABLE_POS_ONDECK[0], f"tiprack_200_{p200_RACK_COUNT}" + ) + AVAILABLE_POS_ONDECK.pop(0) + p200_RACKS_ONDECK.append(addtiprack) + p200_RACKS_PIPET.append(addtiprack) + p1000.tip_racks = p200_RACKS_PIPET + elif len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_200ul", AVAILABLE_POS_OFFDECK[0], f"tiprack_200_{p200_RACK_COUNT}" + ) + AVAILABLE_POS_OFFDECK.pop(0) + p200_RACKS_OFFDECK.append(addtiprack) + p200_RACKS_PIPET.append(addtiprack) + p1000.tip_racks = p200_RACKS_PIPET + TipRackSwap(200) + if p200_TIPS == 0 and len(p200_INITIALTIPS) > 0: + if tipuse == "REUSE" and REUSE_ANY_200_TIPS == True: + if tipset == "WASH" and REUSE_200_TIPS_WASH == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + elif tipset == "ETOH" and REUSE_200_TIPS_ETOH == True: + p1000.pick_up_tip(REUSE_200_TIPS_2[rep]) + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + else: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + if p200_TIPS > 0: + if tipuse == "REUSE" and REUSE_ANY_200_TIPS == True: + if tipset == "WASH" and REUSE_200_TIPS_WASH == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + elif tipset == "ETOH" and REUSE_200_TIPS_ETOH == True: + p1000.pick_up_tip(REUSE_200_TIPS_2[rep]) + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + if tiptype == 50 and tipuse == "STP": + p50.pick_up_tip(stp_50_list[rep]) + if tiptype == 200 and tipuse == "STP": + p1000.pick_up_tip(stp_200_list[rep]) + + def TipDone(tiptype, tipuse, rep, tipset): + # A Function that is called replacing the Tip dropping Step. Values are passed to the function such as tip type, reuse, and which rep (loop number) it is on. + # 1). Checks if it is a Single Tip Pickup, Reusable tip, or if the run is a Dry run, + global TIP_TRASH + if tiptype == 50: + if tipuse == "STP": + p50.drop_tip(str_50_list[rep]) if TIP_TRASH == False else p50.drop_tip() + elif tipuse == "REUSE": + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + if tiptype == 200: + if tipuse == "STP": + p1000.drop_tip(str_200_list[rep]) if TIP_TRASH == False else p1000.drop_tip() + elif tipuse == "REUSE": + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + + def TipRackSwap(tiptype): + # A Function that is called from within the TipCheck function to Swap Tip Racks. + # 1). Sets the values within the Function according to the appropriate tip rack list + # 2). If the Global Value of SWAPOFFDECK = True, it will swap tipracks (rather than dump into the Chute) + # 3). First in line of the RACKS_TO_DUMP is the one removed, can either be p50 or p200, no reusable tips or single Tip Racks + if tiptype == 50: + RACK_EMPTY = RACKS_TO_DUMP[0] + RACK_EMPTY_POS = RACKS_TO_DUMP[0].parent + RACK_NEW = p50_RACKS_OFFDECK[0] + RACK_NEW_POS = p50_RACKS_OFFDECK[0].parent + if tiptype == 200: + RACK_EMPTY = RACKS_TO_DUMP[0] + RACK_EMPTY_POS = RACKS_TO_DUMP[0].parent + RACK_NEW = p200_RACKS_OFFDECK[0] + RACK_NEW_POS = p200_RACKS_OFFDECK[0].parent + if SWAPOFFDECK == True: + SWAPSPOT.append(RACK_NEW_POS) + SWAPSPOT.append(RACK_EMPTY_POS) + protocol.comment("EMPTY POS " + str(SWAPSPOT[0])) + protocol.comment("RACK LEAVING THIS OFF DECK POS " + str(SWAPSPOT[1])) + protocol.comment("EMPTY RACK LEAVING THIS POS, MAKING IT THE NEW EMPTY POS " + str(SWAPSPOT[2])) + protocol.move_labware(labware=RACK_NEW, new_location=SWAPSPOT[0], use_gripper=USE_GRIPPER) + protocol.move_labware(labware=RACK_EMPTY, new_location=SWAPSPOT[1], use_gripper=USE_GRIPPER) + SWAPSPOT.pop(0) + SWAPSPOT.pop(0) + if tiptype == 50: + p50_RACKS_ONDECK.append(RACK_NEW) + RACKS_TO_DUMP.pop(0) + p50_RACKS_ONDECK.pop(0) + p50_RACKS_OFFDECK.pop(0) + if tiptype == 200: + p200_RACKS_ONDECK.append(RACK_NEW) + RACKS_TO_DUMP.pop(0) + p200_RACKS_ONDECK.pop(0) + p200_RACKS_OFFDECK.pop(0) + else: + SWAPS_POT = [RACK_EMPTY_POS] + protocol.move_labware(labware=RACK_EMPTY, new_location=TRASH, use_gripper=USE_GRIPPER) + protocol.move_labware(labware=RACK_NEW, new_location=SWAPS_POT[0], use_gripper=USE_GRIPPER) + if tiptype == 50: + p50_RACKS_ONDECK.append(RACK_NEW) + p50_RACKS_DROPPED.append(RACK_EMPTY) + RACKS_TO_DUMP.pop(0) + p50_RACKS_ONDECK.pop(0) + p50_RACKS_OFFDECK.pop(0) + if tiptype == 200: + p200_RACKS_ONDECK.append(RACK_NEW) + p200_RACKS_DROPPED.append(RACK_EMPTY) + RACKS_TO_DUMP.pop(0) + p200_RACKS_ONDECK.pop(0) + p200_RACKS_OFFDECK.pop(0) + + def PlateUnstack(Startposition, Stopposition): + # A Function that creates a plate, grips it based on offsets mimicking the stacked plates height, and moves it to a new position, + # This is a Standin Function until real functionality for plate unstacking is added. + global PLATE_STACKED + if PLATE_STACKED == 7: + sample_plate_1 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_1, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 6: + sample_plate_2 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_2, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 5: + sample_plate_3 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_3, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 4: + sample_plate_4 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_4, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 3: + sample_plate_5 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_5, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 2: + sample_plate_6 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_6, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 1: + sample_plate_7 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_7, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": 0}, + drop_offset=deck_drop_offset, + ) + + # ======================= SIMPLE SETUP ARRANGEMENT ====================== + # This is a condensed, simpler deck layout arrangement based on position. There are 2 sections, one with all the modules on deck (the NGS Workstation setup) and one without. + # This uses the DefinePosition function listed earlier, it asks for: tiptype (None, 50 or 200), position ('A1', etc.), and Status ('OPEN' for any tip, or the special uses as below) + # List all empty positions avaiable. + # DefinePosition(None,'A2','OPEN') <-- Basic, open for either tip type + # DefinePosition(None,'A2','CLOSED') <-- Tip Location is closed, used just for keeping track for the user + # DefinePosition(200,'A2','REUSE_200_1TIPS') <-- Reusable 200 tips + # DefinePosition(200,'A2','STP_200') <-- Single Tip Pickup 200 tips + # DefinePosition(200,'A2','STR_200') <-- Single Tip Return for 200 tips (testing purposes) + # Then there is a block of code for whether or not the trash is a CHUTE or BIN, note that with a BIN position A4 is not available. + + # ========== FIRST ROW =========== + if ONDECK_THERMO == True: + thermocycler = protocol.load_module("thermocycler module gen2") + if NEW_PLATE == True: + sample_plate_2 = thermocycler.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "Sample Plate 2") + else: + pass + else: + if NEW_PLATE == True: + sample_plate_2 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "A1", "Sample Plate 2") + else: + DefinePosition(None, "A1", "OPEN") + DefinePosition(None, "A2", "OPEN") + # ========== SECOND ROW ========== + if ONDECK_THERMO == True: + pass + else: + DefinePosition(None, "B1", "OPEN") + DefinePosition(None, "B2", "OPEN") + DefinePosition(50, "B3", "REUSE_50_TIPS") + # ========== THIRD ROW =========== + if ONDECK_TEMP == True: + temp_block = protocol.load_module("temperature module gen2", "C1") + temp_adapter = temp_block.load_adapter("opentrons_96_well_aluminum_block") + sample_plate_1 = temp_adapter.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "Sample Plate 1") + else: + sample_plate_1 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "C1", "Sample Plate 1") + reservoir = ( + protocol.load_labware("nest_12_reservoir_15ml", "C2", "Reservoir") + if RES_TYPE_96x == False + else protocol.load_labware("nest_96_wellplate_2ml_deep", "C2") + ) + DefinePosition(200, "C3", "REUSE_200_2TIPS") + # ========== FOURTH ROW ========== + if ONDECK_HEATERSHAKER == True: + heatershaker = protocol.load_module("heaterShakerModuleV1", "D1") + hs_adapter = heatershaker.load_adapter("opentrons_96_deep_well_adapter") + CleanupPlate = hs_adapter.load_labware("nest_96_wellplate_2ml_deep") + else: + CleanupPlate = protocol.load_labware("nest_96_wellplate_2ml_deep", "D1") + mag_block = protocol.load_module("magneticBlockV1", "D2") + # ============ TRASH ============= + if TRASH_POSITION == "BIN": + TRASH = protocol.load_trash_bin("A3") + DefinePosition(None, "D3", "OPEN") + DefinePosition(None, "B4", "OPEN") + DefinePosition(None, "C4", "OPEN") + DefinePosition(None, "D4", "OPEN") + OFFDECK_LIST = ["B4", "C4", "D4"] + if TRASH_POSITION == "CHUTE": + TRASH = protocol.load_waste_chute() + DefinePosition(None, "A3", "OPEN") + DefinePosition(None, "A4", "OPEN") + DefinePosition(None, "B4", "OPEN") + DefinePosition(None, "C4", "OPEN") + DefinePosition(None, "D4", "OPEN") + OFFDECK_LIST = ["A4", "B4", "C4", "D4"] + + # If SWAPOFFDECK = True (Meaning swapping empty On Deck Racks with New Off Deck Racks), removes the first listed tip position to keep it empty for temporary space. + if SWAPOFFDECK == True: + SWAPSPOT.append(AVAILABLE_POS_ONDECK[0]) + AVAILABLE_POS_ONDECK.pop(0) + + # Reverse the lists of Positions for accessibility (First Checked On Deck Slot is D4, Off Deck is D4) + AVAILABLE_POS_ONDECK.reverse() + AVAILABLE_POS_OFFDECK.reverse() + OFFDECK_LIST.reverse() + + # ============================ RESERVOIR ================================ + AMPure = reservoir["A1"] + EtOH_1 = reservoir["A4"] + EtOH_2 = reservoir["A5"] + EtOH_3 = reservoir["A6"] + RSB = reservoir["A7"] + Liquid_trash_well_3 = reservoir["A10"] + Liquid_trash_well_2 = reservoir["A11"] + Liquid_trash_well_1 = reservoir["A12"] + + # ======================= TIP AND SAMPLE TRACKING ======================= + # This is a list of each column to be used in the protocol, as well as any intermediate or final sample positions. + # column_1_list = [f'A{i}' for i in range(1, COLUMNS + 1)] <-- This is a Simple list of 'A1' through 'A12', meaning a full plate. + # Example Protocols can look like this: + # if COLUMNS == 3: + # column_1_list = ['A1','A2','A3'] <-- Initial 3 columns of Samples + # column_2_list = ['A4','A5','A6'] <-- Final 3 columns of Samples + if COLUMNS == 1: + column_1_list = ["A1"] # Plate 1 / Cleanup 1 + if NEW_PLATE == True: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1"] # Cleanup 2 + else: + column_2_list = ["A2"] # Cleanup 2 + column_3_list = ["A1"] # Plate 2 + else: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1"] # Cleanup 2 + else: + column_2_list = ["A2"] # Cleanup 2 + column_3_list = ["A2"] # Plate 1 + if COLUMNS == 2: + column_1_list = ["A1", "A2"] # Plate 1 / Cleanup 1 + if NEW_PLATE == True: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1", "A2"] # Cleanup 2 + else: + column_2_list = ["A3", "A4"] # Cleanup 2 + column_3_list = ["A1", "A2"] # Plate 2 + else: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1", "A2"] # Cleanup 2 + else: + column_2_list = ["A3", "A4"] # Cleanup 2 + column_3_list = ["A3", "A4"] # Plate 1 + if COLUMNS == 3: + column_1_list = [ + "A1", + "A2", + "A3", + ] # Plate 1 / Cleanup 1 + if NEW_PLATE == True: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1", "A2", "A3"] # Cleanup 2 + else: + column_2_list = ["A4", "A5", "A6"] # Cleanup 2 + column_3_list = ["A1", "A2", "A3"] # Plate 2 + else: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1", "A2", "A3"] # Cleanup 2 + else: + column_2_list = ["A4", "A5", "A6"] # Cleanup 2 + column_3_list = ["A4", "A5", "A6"] # Plate 1 + if COLUMNS == 4: + column_1_list = ["A1", "A2", "A3", "A4"] # Plate 1 / Cleanup 1 + if NEW_PLATE == True: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1", "A2", "A3", "A4"] # Cleanup 2 + else: + column_2_list = ["A5", "A6", "A7", "A8"] # Cleanup 2 + column_3_list = ["A1", "A2", "A3", "A4"] # Plate 2 + else: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1", "A2", "A3", "A4"] # Cleanup 2 + else: + column_2_list = ["A5", "A6", "A7", "A8"] # Cleanup 2 + column_3_list = ["A5", "A6", "A7", "A8"] # Plate 1 + if COLUMNS == 5: + column_1_list = ["A1", "A2", "A3", "A4", "A5"] # Plate 1 / Cleanup 1 + if NEW_PLATE == True: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1", "A2", "A3", "A4", "A5"] # Cleanup 2 + else: + column_2_list = ["A6", "A7", "A8", "A9", "A10"] # Cleanup 2 + column_3_list = ["A1", "A2", "A3", "A4", "A5"] # Plate 2 + else: + if DOUBLEDSIDED == "Single Sided": + column_2_list = [ + "A1", + "A2", + "A3", + "A4", + "A5", + ] # Cleanup 2 + else: + column_2_list = ["A6", "A7", "A8", "A9", "A10"] # Cleanup 2 + column_3_list = ["A6", "A7", "A8", "A9", "A10"] # Plate 1 + if COLUMNS == 6: + column_1_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Plate 1 / Cleanup 1 + if NEW_PLATE == True: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Cleanup 2 + else: + column_2_list = ["A7", "A8", "A9", "A10", "A11", "A12"] # Cleanup 2 + column_3_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Plate 2 + else: + if DOUBLEDSIDED == "Single Sided": + column_2_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Cleanup 2 + else: + column_2_list = ["A7", "A8", "A9", "A10", "A11", "A12"] # Cleanup 2 + column_3_list = ["A7", "A8", "A9", "A10", "A11", "A12"] # Plate 1 + + # ============================ CUSTOM OFFSETS =========================== + # These are Custom Offsets which are a PER INSTRUMENT Setting, to account for slight adjustments of the gripper calibration or labware. + if CUSTOM_OFFSETS == True: + PCRPlate_Z_offset = 0 + Deepwell_Z_offset = 1 + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": -2, "z": 0} + hs_pick_up_offset = {"x": 0, "y": -2, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 0.0, "z": 0.5} + mb_pick_up_offset = {"x": 0, "y": -2, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": 0, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # DECK OFFSETS + deck_drop_offset = {"x": 0, "y": 0, "z": 0} + deck_pick_up_offset = {"x": 0, "y": 0, "z": 0} + else: + PCRPlate_Z_offset = 0 + Deepwell_Z_offset = 0 + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": 0, "z": 0} + hs_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 0.0, "z": 0} + mb_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": 0, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # DECK OFFSETS + deck_drop_offset = {"x": 0, "y": 0, "z": 0} + deck_pick_up_offset = {"x": 0, "y": 0, "z": 0} + + # ================================================================================================= + # ========================================= PROTOCOL START ======================================== + # ================================================================================================= + if ONDECK_THERMO == True: + thermocycler.open_lid() + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.pause("Ready") + if ONDECK_HEATERSHAKER == True: + heatershaker.close_labware_latch() + Liquid_trash = Liquid_trash_well_1 + EtOH = EtOH_1 + # ================================================================================================= + # ========================================= PROTOCOL START ======================================== + # ================================================================================================= + + if STEP_CLEANUP == 1: + protocol.comment("==============================================") + protocol.comment("--> Cleanup 1") + protocol.comment("==============================================") + + protocol.comment("--> Transferring Sample to CleanupPlate") + TransferSup = int(INPUTVOLUME / math.ceil(INPUTVOLUME / 50)) + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.75 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # ============================= + for loop, X in enumerate(column_1_list): + TipCheck(50, None, loop, None) + for Y in range(math.ceil(INPUTVOLUME / 50)): + p50.aspirate(TransferSup, sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 0.25)) + p50.dispense(TransferSup, CleanupPlate[column_1_list[loop]].bottom(z=Deepwell_Z_offset + 1)) + TipDone(50, None, loop, None) + # ============================= + + protocol.comment("--> ADDING AMPure (0.8x)") + SampleVol = INPUTVOLUME + AMPureMixRPM = SHAKE_RPM + AMPureMixTime = SHAKE_TIME * 60 if DRYRUN == False else 0.1 * 60 # Seconds + AMPurePremix = 3 if DRYRUN == False else 1 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # ============================= + for loop, X in enumerate(column_1_list): + AMPureVol = BEADRATIO * INPUTVOLUME + TipCheck(200, None, loop, None) + p1000.aspirate(AMPureVol, AMPure.bottom(z=Deepwell_Z_offset + 1), rate=0.25) + p1000.dispense(AMPureVol, AMPure.bottom(z=Deepwell_Z_offset + 1), rate=0.25) + p1000.aspirate(AMPureVol + 3, AMPure.bottom(z=Deepwell_Z_offset + 1), rate=0.25) + p1000.dispense(3, AMPure.bottom(z=Deepwell_Z_offset + 1), rate=0.25) + p1000.default_speed = 5 + p1000.move_to(AMPure.top(z=-3)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(AMPure.top().move(types.Point(x=4, z=-3))) + p1000.move_to(AMPure.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(AMPureVol, CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3), rate=0.5) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 7)) + p1000.default_speed = 100 + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3)) + if TIP_MIX == False: + AmpureMixRep = 10 + if TIP_MIX == True: + AmpureMixRep = 2 + for Mix in range(AmpureMixRep): + p1000.aspirate(70, rate=0.5) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.25)) + p1000.aspirate(20, rate=0.5) + p1000.dispense(20, rate=0.5) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3)) + p1000.dispense(70, rate=0.5) + Mix += 1 + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 7)) + p1000.default_speed = 400 + p1000.move_to(CleanupPlate[X].top(z=-3)) + protocol.delay(seconds=1) + p1000.blow_out(CleanupPlate[X].top(z=-3)) + p1000.touch_tip(speed=100) + p1000.move_to(CleanupPlate[X].top(z=5)) + p1000.move_to(CleanupPlate[X].top(z=0)) + p1000.move_to(CleanupPlate[X].top(z=5)) + TipDone(200, None, loop, None) + # ============================= + if TIP_MIX == True: + heatershaker.set_and_wait_for_shake_speed(rpm=AMPureMixRPM) + protocol.delay(AMPureMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM HEATER SHAKER TO MAG PLATE + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + if DOUBLEDSIDED == "Single Sided": + + protocol.comment("--> Transferring Sample to CleanupPlate") + # TransferReps = math.ceil((INPUTVOLUME+(BEADRATIO*INPUTVOLUME))/50) + # TransferSup = 50 if int((INPUTVOLUME+(BEADRATIO*INPUTVOLUME))/TransferReps)+3 >=50 else int((INPUTVOLUME+(BEADRATIO*INPUTVOLUME))/TransferReps)+3 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.75 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # ============================= + for loop, X in enumerate(column_1_list): + TransferReps = math.ceil((INPUTVOLUME + (BEADRATIO * INPUTVOLUME)) / 50) + TransferSup = ( + 50 + if int((INPUTVOLUME + (BEADRATIO * INPUTVOLUME)) / TransferReps) + 3 >= 50 + else int((INPUTVOLUME + (BEADRATIO * INPUTVOLUME)) / TransferReps) + 3 + ) + TipCheck(50, None, loop, None) + for Y in range(TransferReps): + p50.aspirate(TransferSup, CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.2)) + p50.dispense(TransferSup, CleanupPlate[column_2_list[loop]].bottom(z=Deepwell_Z_offset + 1)) + TipDone(50, None, loop, None) + # ============================= + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM MAG PLATE TO HEATER SHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> ADDING AMPure (0.8x)") + AMPureVol = BEADRATIO_2 * INPUTVOLUME + SampleVol = INPUTVOLUME + AMPureMixRPM = SHAKE_RPM + AMPureMixTime = SHAKE_TIME * 60 if DRYRUN == False else 0.1 * 60 # Seconds + AMPurePremix = 3 if DRYRUN == False else 1 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # ============================= + for loop, X in enumerate(column_2_list): + TipCheck(200, None, loop, None) + p1000.aspirate(AMPureVol, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(AMPureVol, AMPure.bottom(z=1), rate=0.25) + p1000.aspirate(AMPureVol + 3, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(3, AMPure.bottom(z=1), rate=0.25) + p1000.default_speed = 5 + p1000.move_to(AMPure.top(z=-3)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(AMPure.top().move(types.Point(x=4, z=-3))) + p1000.move_to(AMPure.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(AMPureVol, CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3), rate=0.5) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 7)) + p1000.default_speed = 100 + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3)) + if TIP_MIX == False: + AmpureMixRep = 10 + if TIP_MIX == True: + AmpureMixRep = 2 + for Mix in range(AmpureMixRep): + p1000.aspirate(70, rate=0.5) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.25)) + p1000.aspirate(20, rate=0.5) + p1000.dispense(20, rate=0.5) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3)) + p1000.dispense(70, rate=0.5) + Mix += 1 + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 7)) + p1000.default_speed = 400 + p1000.move_to(CleanupPlate[X].top(z=-3)) + protocol.delay(seconds=1) + p1000.blow_out(CleanupPlate[X].top(z=-3)) + p1000.touch_tip(speed=100) + p1000.move_to(CleanupPlate[X].top(z=5)) + p1000.move_to(CleanupPlate[X].top(z=0)) + p1000.move_to(CleanupPlate[X].top(z=5)) + TipDone(200, None, loop, None) + # ============================= + if TIP_MIX == True: + heatershaker.set_and_wait_for_shake_speed(rpm=AMPureMixRPM) + protocol.delay(AMPureMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM HEATER SHAKER TO MAG PLATE + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + ActualRemoveSup = 8 * (BEADRATIO * INPUTVOLUME) + INPUTVOLUME + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # ============================= + for loop, X in enumerate(column_2_list): + TipCheck(200, "REUSE", loop, "ETOH") + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 2)) + p1000.aspirate(RemoveSup - 100) + protocol.delay(minutes=0.1) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0)) + p1000.aspirate(100) + p1000.default_speed = 200 + p1000.move_to(CleanupPlate[X].top(z=2)) + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop, "ETOH") + # ============================= + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 200 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default + # ============================= + if ETOH_1_AirMultiDis == True: + TipCheck(200, None, loop, None) + for loop, X in enumerate(column_2_list): + # =======EtOH Volume Check======== + ETOHVOL += ETOHMaxVol * 8 + protocol.comment("Taking " + str((ETOHMaxVol * 8)) + "ul from " + str(ETOHVOL)) + if ETOHVOL < 14400: + EtOH = EtOH_1 + if ETOHVOL >= 14400 and ETOHVOL < 28800: + EtOH = EtOH_2 + if ETOHVOL >= 28800: + EtOH = EtOH_3 + # ================================ + p1000.aspirate(ETOHMaxVol, EtOH.bottom(z=1)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(EtOH.top(z=-5)) + p1000.move_to(EtOH.top(z=0)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(EtOH.top().move(types.Point(x=4, z=-3))) + p1000.move_to(EtOH.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(CleanupPlate[X].top(z=7)) + p1000.dispense(ETOHMaxVol) + protocol.delay(minutes=0.1) + p1000.blow_out(CleanupPlate[X].top(z=2)) + p1000.move_to(CleanupPlate[X].top(z=5)) + p1000.move_to(CleanupPlate[X].top(z=2)) + p1000.move_to(CleanupPlate[X].top(z=5)) + TipDone(200, None, loop, None) + else: + for loop, X in enumerate(column_2_list): + TipCheck(200, None, loop, None) + # =======EtOH Volume Check======== + ETOHVOL += ETOHMaxVol * 8 + protocol.comment("Taking " + str((ETOHMaxVol * 8)) + "ul from " + str(ETOHVOL)) + if ETOHVOL < 14400: + EtOH = EtOH_1 + if ETOHVOL >= 14400 and ETOHVOL < 28800: + EtOH = EtOH_2 + if ETOHVOL >= 28800: + EtOH = EtOH_3 + # ================================ + p1000.aspirate(ETOHMaxVol, EtOH.bottom(z=1)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(EtOH.top(z=-5)) + p1000.move_to(EtOH.top(z=0)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(EtOH.top().move(types.Point(x=4, z=-3))) + p1000.move_to(EtOH.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(CleanupPlate[X].top(z=-5)) + p1000.dispense(ETOHMaxVol) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.move_to(CleanupPlate[X].top(z=5)) + p1000.move_to(CleanupPlate[X].top(z=0)) + p1000.move_to(CleanupPlate[X].top(z=5)) + TipDone(200, None, loop, None) + # ============================= + if DRYRUN == False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + RemoveSup = 200 + ActualRemoveSup = 200 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default + # ============================= + for loop, X in enumerate(column_2_list): + TipCheck(200, "REUSE", loop, "ETOH") + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 2)) + p1000.aspirate(RemoveSup - 100) + protocol.delay(minutes=0.1) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0)) + p1000.aspirate(100) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 7)) + p1000.default_speed = 200 + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop, "ETOH") + # ============================= + + if DRYRUN == False: + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual Wash") + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # ============================= + for loop, X in enumerate(column_2_list): + TipCheck(50, None, loop, None) + p50.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0)) + p50.aspirate(50, rate=0.25) + TipDone(50, None, loop, None) + # ============================= + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM MAG PLATE TO HEATER SHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Adding RSB") + RSBVol = RESUSPENSION + 2 + RSBMixRPM = SHAKE_RPM + RSBMixTime = SHAKE_TIME * 60 if DRYRUN == False else 0.1 * 60 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # ============================= + for loop, X in enumerate(column_2_list): + TipCheck(50, "REUSE", loop, None) + p50.aspirate(RSBVol, RSB.bottom(z=1)) + p50.move_to(CleanupPlate.wells_by_name()[X].top(z=3)) + p50.dispense(RSBVol) + if TIP_MIX == False: + RSBMixRep = 10 + if TIP_MIX == True: + RSBMixRep = 2 + p50.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.25)) + for Mix in range(RSBMixRep): + p50.aspirate(RSBVol, rate=0.5) + p50.dispense(RSBVol, rate=0.5) + Mix += 1 + p50.blow_out(CleanupPlate.wells_by_name()[X].top(z=3)) + TipDone(50, "REUSE", loop, None) + # ============================= + if TIP_MIX == True: + heatershaker.set_and_wait_for_shake_speed(rpm=RSBMixRPM) + protocol.delay(RSBMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM HEATER SHAKER TO MAG PLATE + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=3) + + protocol.comment("--> Transferring Supernatant") + TransferSup = RESUSPENSION + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # ============================= + for loop, X in enumerate(column_2_list): + TipCheck(50, "REUSE", loop, None) + p50.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0)) + p50.aspirate(TransferSup) + if NEW_PLATE == True: + p50.dispense(TransferSup, sample_plate_2[column_3_list[loop]].bottom(z=PCRPlate_Z_offset + 0.5)) + p50.blow_out(sample_plate_2[column_3_list[loop]].bottom(z=PCRPlate_Z_offset + 2)) + else: + p50.dispense(TransferSup, sample_plate_1[column_3_list[loop]].bottom(z=PCRPlate_Z_offset + 0.5)) + p50.blow_out(sample_plate_1[column_3_list[loop]].bottom(z=PCRPlate_Z_offset + 2)) + TipDone(50, "REUSE", loop, None) + # ============================= + + # ================================================================================================= + # ========================================== PROTOCOL END ========================================= + # ================================================================================================= + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + # ================================================================================================= + # ========================================== PROTOCOL END ========================================= + # ================================================================================================= + + protocol.comment("==============================================") + protocol.comment("--> Report") + protocol.comment("==============================================") + # This is a section that will print out the various lists to help keep track of modifying the protocol, set the REPORT step to False to ignore. + if REPORT == True: + protocol.comment("REUSE_50_TIPS " + str(REUSE_50_TIPS)) + protocol.comment("p50_INITIALTIPS " + str(p50_INITIALTIPS)) + protocol.comment("REUSE_200_TIPS_1 " + str(REUSE_200_TIPS_1)) + protocol.comment("REUSE_200_TIPS_2 " + str(REUSE_200_TIPS_2)) + protocol.comment("p200_INITIALTIPS " + str(p200_INITIALTIPS)) + protocol.comment("SWAPSPOT " + str(SWAPSPOT)) + protocol.comment("AVAILABLE_POS_ONDECK " + str(AVAILABLE_POS_ONDECK)) + protocol.comment("AVAILABLE_POS_OFFDECK " + str(AVAILABLE_POS_OFFDECK)) + protocol.comment("REUSE_50_TIPS_COUNT " + str(REUSE_50_TIPS_COUNT)) + protocol.comment("REUSE_200_TIPS_COUNT " + str(REUSE_200_TIPS_COUNT)) + protocol.comment("p50_RACKS_ONDECK " + str(p50_RACKS_ONDECK)) + protocol.comment("p50_RACKS_OFFDECK " + str(p50_RACKS_OFFDECK)) + protocol.comment("p50_RACKS_DROPPED " + str(p50_RACKS_DROPPED)) + protocol.comment("p50_TIPS " + str(p50_TIPS)) + protocol.comment("p50_RACKS_PIPET " + str(p50_RACKS_PIPET)) + protocol.comment("p200_RACKS_ONDECK " + str(p200_RACKS_ONDECK)) + protocol.comment("p200_RACKS_OFFDECK " + str(p200_RACKS_OFFDECK)) + protocol.comment("p200_RACKS_DROPPED " + str(p200_RACKS_DROPPED)) + protocol.comment("p200_TIPS " + str(p200_TIPS)) + protocol.comment("p200_RACKS_PIPET " + str(p200_RACKS_PIPET)) + protocol.comment("RACKS_TO_DUMP " + str(RACKS_TO_DUMP)) + protocol.comment("ETOHVOL " + str(ETOHVOL)) + protocol.comment("Liquid_trash " + str(WASTEVOL)) + + # This is a section that is used to define liquids, and label wells, this is optional, and unconnected from the rest of the protocol, used only for the App and Website + # This is at the end because it adds lines of code to the runtime that can be at the end rather than the beginning, since it has no effect on the protocol setps. + if LABEL == True: + # PROTOCOL SETUP - LABELING + + # ======== ESTIMATING LIQUIDS ======= + Sample_Volume = INPUTVOLUME + AMPure_Volume = COLUMNS * ((BEADRATIO + BEADRATIO_2) * INPUTVOLUME) + ETOH_Volume = 8 * COLUMNS * ((150 * 2)) + RSB_Volume = 8 * COLUMNS * (RESUSPENSION) + + TotalColumn = ["A", "B", "C", "D", "E", "F", "G", "H"] + UsedColumn = ["A", "B", "C", "D", "E", "F", "G", "H"] + + # ======== DEFINING LIQUIDS ======= + AMPure = protocol.define_liquid(name="EtOH", description="AMPure Beads", display_color="#704848") # 704848 = 'AMPure Brown' + EtOH = protocol.define_liquid(name="EtOH", description="80% Ethanol", display_color="#9ACECB") # 9ACECB = 'Ethanol Blue' + RSB = protocol.define_liquid(name="RSB", description="Resuspension Buffer", display_color="#00FFF2") # 00FFF2 = 'Base Light Blue' + Liquid_trash_well = protocol.define_liquid( + name="Liquid_trash_well", description="Liquid Trash", display_color="#9B9B9B" + ) # 9B9B9B = 'Liquid Trash Grey' + Sample = protocol.define_liquid(name="Sample", description="Sample", display_color="#52AAFF") # 52AAFF = 'Sample Blue' + Final_Sample = protocol.define_liquid( + name="Final_Sample", description="Final Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + + # ======== LOADING LIQUIDS ======= + if RES_TYPE_96x == False: + reservoir.wells_by_name()["A1"].load_liquid(liquid=AMPure, volume=AMPure_Volume * 8) + if ETOH_Volume >= 15000: + reservoir.wells_by_name()["A3"].load_liquid(liquid=EtOH, volume=15000) + if ETOH_Volume >= 15000 and ETOH_Volume < 30000: + reservoir.wells_by_name()["A3"].load_liquid(liquid=EtOH, volume=15000) + reservoir.wells_by_name()["A4"].load_liquid(liquid=EtOH, volume=15000) + if ETOH_Volume >= 30000: + reservoir.wells_by_name()["A3"].load_liquid(liquid=EtOH, volume=15000) + reservoir.wells_by_name()["A4"].load_liquid(liquid=EtOH, volume=15000) + reservoir.wells_by_name()["A5"].load_liquid(liquid=EtOH, volume=15000) + reservoir.wells_by_name()["A7"].load_liquid(liquid=RSB, volume=RSB_Volume * 8) + reservoir.wells_by_name()["A10"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()["A11"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()["A12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if RES_TYPE_96x == True: + for loop, X in enumerate(UsedColumn): + reservoir.wells_by_name()[X + "1"].load_liquid(liquid=AMPure, volume=AMPure_Volume) + if ETOH_Volume >= 15000: + reservoir.wells_by_name()[X + "3"].load_liquid(liquid=EtOH, volume=15000) + if ETOH_Volume >= 15000 and ETOH_Volume < 30000: + reservoir.wells_by_name()[X + "3"].load_liquid(liquid=EtOH, volume=15000) + reservoir.wells_by_name()[X + "4"].load_liquid(liquid=EtOH, volume=15000) + if ETOH_Volume >= 30000: + reservoir.wells_by_name()[X + "3"].load_liquid(liquid=EtOH, volume=15000) + reservoir.wells_by_name()[X + "4"].load_liquid(liquid=EtOH, volume=15000) + reservoir.wells_by_name()[X + "5"].load_liquid(liquid=EtOH, volume=15000) + reservoir.wells_by_name()[X + "7"].load_liquid(liquid=RSB, volume=RSB_Volume) + reservoir.wells_by_name()[X + "10"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()[X + "11"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()[X + "12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if COLUMNS >= 1: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS <= 6: + sample_plate_1.wells_by_name()[X + str(COLUMNS + 1)].load_liquid(liquid=Final_Sample, volume=0) + else: + sample_plate_2.wells_by_name()[X + "1"].load_liquid(liquid=Final_Sample, volume=0) + if COLUMNS >= 2: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS <= 6: + sample_plate_1.wells_by_name()[X + str(COLUMNS + 2)].load_liquid(liquid=Final_Sample, volume=0) + else: + sample_plate_2.wells_by_name()[X + "2"].load_liquid(liquid=Final_Sample, volume=0) + if COLUMNS >= 3: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "3"].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS <= 6: + sample_plate_1.wells_by_name()[X + str(COLUMNS + 3)].load_liquid(liquid=Final_Sample, volume=0) + else: + sample_plate_2.wells_by_name()[X + "3"].load_liquid(liquid=Final_Sample, volume=0) + if COLUMNS >= 4: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "4"].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS <= 6: + sample_plate_1.wells_by_name()[X + str(COLUMNS + 4)].load_liquid(liquid=Final_Sample, volume=0) + else: + sample_plate_2.wells_by_name()[X + "4"].load_liquid(liquid=Final_Sample, volume=0) + if COLUMNS >= 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS <= 6: + sample_plate_1.wells_by_name()[X + str(COLUMNS + 5)].load_liquid(liquid=Final_Sample, volume=0) + else: + sample_plate_2.wells_by_name()[X + "5"].load_liquid(liquid=Final_Sample, volume=0) + if COLUMNS >= 6: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "6"].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS <= 6: + sample_plate_1.wells_by_name()[X + str(COLUMNS + 6)].load_liquid(liquid=Final_Sample, volume=0) + else: + sample_plate_2.wells_by_name()[X + "6"].load_liquid(liquid=Final_Sample, volume=0) diff --git a/app-testing/files/protocols/pl_BCApeptideassay.py b/app-testing/files/protocols/pl_BCApeptideassay.py new file mode 100644 index 00000000000..e9fd42668d7 --- /dev/null +++ b/app-testing/files/protocols/pl_BCApeptideassay.py @@ -0,0 +1,594 @@ +metadata = {"protocolName": "Pierce BCA Peptide Assay", "author": "Boren Lin, Opentrons", "description": ""} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.18", +} + +######################## + +NUM_REPLICATE = 2 +VOL_SAMPLE = 20 +VOL_WR = 180 + + +def add_parameters(parameters): + parameters.add_int( + variable_name="reagent_prep_on_deck", + display_name="Working reagent prep on deck", + description="Prepare working reagent (Reagent A and B mixture) on deck?", + default=1, + choices=[{"display_name": "Yes", "value": 1}, {"display_name": "No", "value": 0}], + ) + parameters.add_int( + variable_name="dilution_on_deck", + display_name="Samples diluted on deck", + description="Prepare sample dilutions (2-fold serial dilutions: 1x, 0.5x, 0.25x, 0.125x)?", + default=0, + choices=[{"display_name": "Yes", "value": 1}, {"display_name": "No", "value": 0}], + ) + parameters.add_int( + variable_name="heating", + display_name="Heating module", + description="Color development - incubation on Heater-shaker Module or Temperature Module?", + default=1, + choices=[{"display_name": "Heater-shaker Module", "value": 1}, {"display_name": "Temperature Module", "value": 2}], + ) + parameters.add_int( + variable_name="time_incubation", + display_name="Incubation Time", + description="Color development - incubation for how long (min)?", + default=15, + minimum=0, + maximum=120, + ) + parameters.add_int( + variable_name="num_sample", + display_name="Number of Samples", + description="Number of samples to be assayed. If samples need to be diluted, maximum: 12", + default=48, + minimum=1, + maximum=48, + ) + parameters.add_int( + variable_name="pipet_location", + display_name="P1000 1-ch Position", + description="How P1000 single channel pipette is mounted?", + default=1, + choices=[{"display_name": "on the right", "value": 1}, {"display_name": "on the left", "value": 2}], + ) + + +def run(ctx): + global reagent_prep_on_deck + global dilution_on_deck + global heating + global time_incubation + global num_sample + global pipet_location + + global num_sample_initial + global num_sample_final + global num_col + global vol_wr_well_1 + global vol_wr_well_2 + global vol_a_well_1 + global vol_a_well_2 + global vol_b_well_1 + global vol_b_well_2 + global vol_c_well_1 + global vol_c_well_2 + + reagent_prep_on_deck = ctx.params.reagent_prep_on_deck + dilution_on_deck = ctx.params.dilution_on_deck + heating = ctx.params.heating + time_incubation = ctx.params.time_incubation + num_sample = ctx.params.num_sample + pipet_location = ctx.params.pipet_location + + if dilution_on_deck == 0: + num_sample_final = num_sample + ## standard + unknowns, max. 48 + + else: + num_sample_initial = num_sample + num_sample_final = num_sample * 4 + ## standard + unknowns, max. 12 + + if num_sample_final * NUM_REPLICATE > 96 or num_sample_final == 0: + raise Exception("Invalid sample number") + + num_rxn = num_sample_final * NUM_REPLICATE + num_col = int(num_rxn // 8) + if num_rxn % 8 != 0: + num_col = num_col + 1 + + if num_col > 6: + vol_wr_well_1 = (6 - 1) * VOL_WR * 8 + 2000 + vol_wr_well_2 = (num_col - 6 - 1) * VOL_WR * 8 + 2000 + + else: + vol_wr_well_1 = (num_col - 1) * VOL_WR * 8 + 2000 + vol_wr_well_2 = 0 + + vol_a_well_1 = (vol_wr_well_1 / 100) * 50 + vol_a_well_2 = (vol_wr_well_2 / 100) * 50 + vol_b_well_1 = (vol_wr_well_1 / 100) * 48 + vol_b_well_2 = (vol_wr_well_2 / 100) * 48 + vol_c_well_1 = (vol_wr_well_1 / 100) * 2 + vol_c_well_2 = (vol_wr_well_2 / 100) * 2 + + if pipet_location == 1: + p1k_1_loc = "right" + p1k_8_loc = "left" + else: + p1k_1_loc = "left" + p1k_8_loc = "right" + + # deck layout + if heating == 1: + hs = ctx.load_module("heaterShakerModuleV1", "D1") + hs_adapter = hs.load_adapter("opentrons_universal_flat_adapter") + + elif heating == 2: + temp = ctx.load_module("Temperature Module Gen2", "D3") + temp_adapter = temp.load_adapter("opentrons_aluminum_flat_bottom_plate") + + if reagent_prep_on_deck == 1 or dilution_on_deck == 1: + reagent_stock_rack = ctx.load_labware("opentrons_10_tuberack_nest_4x50ml_6x15ml_conical", "A2", "REAGENTS") + + wr_reservoir = ctx.load_labware("nest_12_reservoir_15ml", "B2", "WORKING REAGENT") + sample_rack_1 = ctx.load_labware("opentrons_24_tuberack_nest_1.5ml_snapcap", "C1", "SAMPLES") + if num_sample_final > 24: + sample_rack_2 = ctx.load_labware("opentrons_24_tuberack_nest_1.5ml_snapcap", "B1", "SAMPLES") + + ctx.load_trash_bin("A3") + + if dilution_on_deck == 1: + tips_200 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A1", "P200 TIPS") + tips_200_loc = tips_200.wells()[:96] + + tips_1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B3", "P1000 TIPS") + tips_1000_loc = tips_1000.wells()[:96] + tips_50 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "C3", "P50 TIPS") + tips_50_loc = tips_50.wells()[:96] + p1k_1 = ctx.load_instrument("flex_1channel_1000", p1k_1_loc) + p1k_8 = ctx.load_instrument("flex_8channel_1000", p1k_8_loc) + + # liquid location + if reagent_prep_on_deck == 1: + reagent_a = reagent_stock_rack.wells()[8] + reagent_b = reagent_stock_rack.wells()[6] + reagent_c = reagent_stock_rack.wells()[0] + + if dilution_on_deck == 1: + buffer = reagent_stock_rack.wells()[2] + + wr_1 = wr_reservoir.wells()[0] + if num_col > 6: + wr_2 = wr_reservoir.wells()[1] + + sample_1 = sample_rack_1.wells()[:24] + if num_sample_final > 24: + sample_2 = sample_rack_2.wells()[:24] + + ## generate desk layout and volume info + if reagent_prep_on_deck == 1: + vol_a = ((num_col - 1) * VOL_WR * 8 + 3000) / 100 * 50 + vol_b = ((num_col - 1) * VOL_WR * 8 + 3000) / 100 * 48 + vol_c = ((num_col - 1) * VOL_WR * 8 + 3000) / 100 * 2 + def_a = ctx.define_liquid(name="Reagent A", description=" ", display_color="#E5E4E2") ## Gray + reagent_stock_rack.wells()[8].load_liquid(liquid=def_a, volume=vol_a) + def_b = ctx.define_liquid(name="Reagent B", description=" ", display_color="#0000FF") ## Blue + reagent_stock_rack.wells()[6].load_liquid(liquid=def_b, volume=vol_b) + def_c = ctx.define_liquid(name="Reagent C", description=" ", display_color="#FFA500") ## Orange + reagent_stock_rack.wells()[0].load_liquid(liquid=def_c, volume=vol_c) + else: + if num_col > 6: + vol_1 = (6 - 1) * VOL_WR * 8 + 2000 + vol_2 = (num_col - 6 - 1) * VOL_WR * 8 + 2000 + def_1 = ctx.define_liquid(name="Working Reagent", description=" ", display_color="#50C878") ## Green + wr_reservoir.wells()[0].load_liquid(liquid=def_1, volume=vol_1) + def_2 = ctx.define_liquid(name="Working Reagent", description=" ", display_color="#50C878") ## Green + wr_reservoir.wells()[1].load_liquid(liquid=def_2, volume=vol_2) + else: + vol_1 = (num_col - 1) * VOL_WR * 8 + 2000 + def_1 = ctx.define_liquid(name="Working Reagent", description=" ", display_color="#50C878") ## Green + wr_reservoir.wells()[0].load_liquid(liquid=def_1, volume=vol_1) + + if dilution_on_deck == 1: + vol_undiluted = (VOL_SAMPLE + 5) * NUM_REPLICATE * 2 + 10 + vol_buffer = (VOL_SAMPLE + 5) * NUM_REPLICATE * 3 * num_sample_initial + 50 + if num_sample_initial > 6: + def_undiluted_1 = ctx.define_liquid( + name="Sample", description="Undiluted Samples, volume per tube (Slot C1)", display_color="#FF0000" + ) ## Red + def_undiluted_2 = ctx.define_liquid( + name="Sample", description="Undiluted Samples, volume per tube (Slot B1)", display_color="#FF0000" + ) ## Red + for p in range(6): + sample_rack_1.rows()[0][p].load_liquid(liquid=def_undiluted_1, volume=vol_undiluted / 6) + for q in range(num_sample_initial - 6): + sample_rack_2.rows()[0][q].load_liquid(liquid=def_undiluted_2, volume=vol_undiluted / (num_sample_initial - 6)) + else: + def_undiluted_1 = ctx.define_liquid( + name="Sample", description="Undiluted Samples, volume per tube (Slot C1)", display_color="#FF0000" + ) ## Red + for p in range(num_sample_initial): + sample_rack_1.rows()[0][p].load_liquid(liquid=def_undiluted_1, volume=vol_undiluted / num_sample_initial) + def_buffer = ctx.define_liquid(name="Buffer", description="Buffer for sample dilution", display_color="#704848") ## Brown + reagent_stock_rack.wells()[2].load_liquid(liquid=def_buffer, volume=vol_buffer) + else: + vol_ = VOL_SAMPLE * NUM_REPLICATE + 10 + if num_sample_final > 24: + def_1 = ctx.define_liquid(name="Sample", description="Samples, volume per tube (Slot C1)", display_color="#FF0000") ## Red + def_2 = ctx.define_liquid(name="Sample", description="Samples, volume per tube (Slot B1)", display_color="#FF0000") ## Red + for p in range(24): + sample_rack_1.wells()[p].load_liquid(liquid=def_1, volume=vol_ / 24) + for q in range(num_sample_final - 24): + sample_rack_2.wells()[q].load_liquid(liquid=def_2, volume=vol_ / (num_sample_final - 24)) + else: + def_1 = ctx.define_liquid(name="Sample", description="Samples, volume per tube (Slot C1)", display_color="#FF0000") ## Red + for p in range(num_sample_final): + sample_rack_1.wells()[p].load_liquid(liquid=def_1, volume=vol_ / num_sample_final) + + # protocol + ## dilution prep + if dilution_on_deck == 1: + p1k_1.pick_up_tip(tips_200_loc[0]) + if num_sample_initial > 6: + for x in range(6): + p1k_1.aspirate((VOL_SAMPLE + 5) * NUM_REPLICATE * 3, buffer) + ctx.delay(seconds=2) + for y in range(3): + end = sample_1[y + x * 4 + 1] + p1k_1.flow_rate.dispense = VOL_SAMPLE * NUM_REPLICATE + p1k_1.dispense((VOL_SAMPLE + 5) * NUM_REPLICATE, end.bottom(z=10)) + ctx.delay(seconds=2) + p1k_1.blow_out(buffer.top(z=0)) + for x in range(num_sample_initial - 6): + p1k_1.aspirate((VOL_SAMPLE + 5) * NUM_REPLICATE * 3, buffer) + ctx.delay(seconds=2) + for y in range(3): + end = sample_2[y + x * 4 + 1] + p1k_1.flow_rate.dispense = VOL_SAMPLE * NUM_REPLICATE + p1k_1.dispense((VOL_SAMPLE + 5) * NUM_REPLICATE, end.bottom(z=10)) + ctx.delay(seconds=2) + else: + for x in range(num_sample_initial): + p1k_1.aspirate((VOL_SAMPLE + 5) * NUM_REPLICATE * 3, buffer) + ctx.delay(seconds=2) + for y in range(3): + end = sample_1[y + x * 4 + 1] + p1k_1.flow_rate.dispense = VOL_SAMPLE * NUM_REPLICATE + p1k_1.dispense((VOL_SAMPLE + 5) * NUM_REPLICATE, end.bottom(z=10)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + + if num_sample_initial > 6: + p1k_1.flow_rate.aspirate = VOL_SAMPLE * NUM_REPLICATE + for x in range(6): + p1k_1.pick_up_tip(tips_200_loc[x + 1]) + for y in range(3): + start = sample_1[x * 4 + y] + end = sample_1[x * 4 + y + 1] + p1k_1.aspirate((VOL_SAMPLE + 5) * NUM_REPLICATE, start) + p1k_1.dispense((VOL_SAMPLE + 5) * NUM_REPLICATE, end.bottom(z=10)) + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + p1k_1.mix(3, VOL_SAMPLE, end) + p1k_1.blow_out(end.top(z=-5)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + for x in range(num_sample_initial - 6): + p1k_1.pick_up_tip(tips_200_loc[x + 7]) + for y in range(3): + start = sample_2[x * 4 + y] + end = sample_2[x * 4 + y + 1] + p1k_1.aspirate((VOL_SAMPLE + 5) * NUM_REPLICATE, start) + p1k_1.dispense((VOL_SAMPLE + 5) * NUM_REPLICATE, end.bottom(z=10)) + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + p1k_1.mix(3, VOL_SAMPLE, end) + p1k_1.blow_out(end.top(z=-5)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + else: + p1k_1.flow_rate.aspirate = VOL_SAMPLE * NUM_REPLICATE + for x in range(num_sample_initial): + p1k_1.pick_up_tip(tips_200_loc[x + 1]) + for y in range(3): + start = sample_1[x * 4 + y] + end = sample_1[x * 4 + y + 1] + p1k_1.aspirate((VOL_SAMPLE + 5) * NUM_REPLICATE, start) + p1k_1.dispense((VOL_SAMPLE + 5) * NUM_REPLICATE, end.bottom(z=10)) + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + p1k_1.mix(3, VOL_SAMPLE, end) + p1k_1.blow_out(end.top(z=-5)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + + ## working reagent prep + if reagent_prep_on_deck == 1: + p1k_1.pick_up_tip(tips_1000_loc[0]) + n = int(vol_a_well_1 // 900) + for _ in range(n): + p1k_1.aspirate(900, reagent_a) + ctx.delay(seconds=2) + p1k_1.dispense(900, wr_1.top(z=0)) + ctx.delay(seconds=2) + if vol_a_well_1 % 900 != 0: + vol_last = vol_a_well_1 - (900 * n) + p1k_1.aspirate(vol_last, reagent_a) + ctx.delay(seconds=2) + p1k_1.dispense(vol_last, wr_1.top(z=0)) + ctx.delay(seconds=2) + if num_col > 6: + nn = int(vol_a_well_2 // 900) + for _ in range(nn): + p1k_1.aspirate(900, reagent_a) + ctx.delay(seconds=2) + p1k_1.dispense(900, wr_2.top(z=0)) + ctx.delay(seconds=2) + if vol_a_well_2 % 900 != 0: + vol_last = vol_a_well_2 - (900 * nn) + p1k_1.aspirate(vol_last, reagent_a) + ctx.delay(seconds=2) + p1k_1.dispense(vol_last, wr_2.top(z=0)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + + p1k_1.pick_up_tip(tips_1000_loc[1]) + n = int(vol_b_well_1 // 900) + for _ in range(n): + p1k_1.aspirate(900, reagent_b) + ctx.delay(seconds=2) + p1k_1.dispense(900, wr_1.top(z=0)) + ctx.delay(seconds=2) + if vol_b_well_1 % 900 != 0: + vol_last = vol_b_well_1 - (900 * n) + p1k_1.aspirate(vol_last, reagent_b) + ctx.delay(seconds=2) + p1k_1.dispense(vol_last, wr_1.top(z=0)) + ctx.delay(seconds=2) + if num_col > 6: + nn = int(vol_b_well_2 // 900) + for _ in range(nn): + p1k_1.aspirate(900, reagent_b) + ctx.delay(seconds=2) + p1k_1.dispense(900, wr_2.top(z=0)) + ctx.delay(seconds=2) + if vol_b_well_2 % 900 != 0: + vol_last = vol_b_well_2 - (900 * nn) + p1k_1.aspirate(vol_last, reagent_b) + ctx.delay(seconds=2) + p1k_1.dispense(vol_last, wr_2.top(z=0)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + + p1k_1.pick_up_tip(tips_1000_loc[2]) + p1k_1.aspirate(vol_c_well_1, reagent_c) + ctx.delay(seconds=2) + p1k_1.dispense(vol_c_well_1, wr_1) + ctx.delay(seconds=2) + p1k_1.blow_out(wr_1.top(z=0)) + if num_col > 6: + p1k_1.aspirate(vol_c_well_2, reagent_c) + ctx.delay(seconds=2) + p1k_1.dispense(vol_c_well_2, wr_2) + ctx.delay(seconds=2) + p1k_1.blow_out(wr_2.top(z=0)) + p1k_1.drop_tip() + + ## working plate filling + working_plate = ctx.load_labware("corning_96_wellplate_360ul_flat", "C2", "WORKING PLATE") + working_plate_lid = ctx.load_labware("corning_96_wellplate_360ul_flat", "D2", "PLATE LID") + rxn_col = working_plate.rows()[0][:12] + rxn_well = working_plate.wells()[:96] + + ### add working reagent + if reagent_prep_on_deck == 1: + p1k_8.pick_up_tip(tips_1000_loc[8]) + if num_col > 6: + for _ in range(20): + p1k_8.aspirate(VOL_WR, wr_2) + p1k_8.dispense(VOL_WR, wr_2.top(z=0)) + for _ in range(20): + p1k_8.aspirate(VOL_WR, wr_1) + p1k_8.dispense(VOL_WR, wr_1.top(z=0)) + else: + p1k_8.pick_up_tip(tips_1000_loc[0]) + + if num_col > 6: + for i in range(2): + p1k_8.aspirate(VOL_WR * 3, wr_1) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[i * 3] + p1k_8.dispense(20, end.top(z=0)) + for j in range(3): + end = rxn_col[i * 3 + j] + p1k_8.dispense(VOL_WR, end.top(z=0)) + ctx.delay(seconds=2) + col = num_col - 6 + if col > 3: + p1k_8.aspirate(VOL_WR * 3, wr_2) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[6] + p1k_8.dispense(20, end.top(z=0)) + for jj in range(3): + end = rxn_col[6 + jj] + p1k_8.dispense(VOL_WR, end.top(z=0)) + ctx.delay(seconds=2) + ii = col - 3 + p1k_8.aspirate(VOL_WR * ii, wr_2) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[9] + p1k_8.dispense(20, end.top(z=0)) + for iii in range(ii): + end = rxn_col[9 + iii] + p1k_8.dispense(VOL_WR, end.top(z=0)) + ctx.delay(seconds=2) + else: + p1k_8.aspirate(VOL_WR * col, wr_2) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[6] + p1k_8.dispense(20, end.top(z=0)) + for jjj in range(col): + end = rxn_col[6 + jjj] + p1k_8.dispense(VOL_WR, end.top(z=0)) + ctx.delay(seconds=2) + else: + if num_col > 3: + p1k_8.aspirate(VOL_WR * 3, wr_1) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[0] + p1k_8.dispense(20, end.top(z=0)) + for g in range(3): + end = rxn_col[g] + p1k_8.dispense(VOL_WR, end.top(z=0)) + ctx.delay(seconds=2) + col = num_col - 3 + p1k_8.aspirate(VOL_WR * col, wr_1) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[3] + p1k_8.dispense(20, end.top(z=0)) + for h in range(col): + end = rxn_col[3 + h] + p1k_8.dispense(VOL_WR, end.top(z=0)) + ctx.delay(seconds=2) + else: + p1k_8.aspirate(VOL_WR * num_col, wr_1) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[0] + p1k_8.dispense(20, end.top(z=0)) + for hh in range(num_col): + end = rxn_col[hh] + p1k_8.dispense(VOL_WR, end.top(z=0)) + ctx.delay(seconds=2) + + p1k_8.drop_tip() + + ### add samples + if num_sample_final > 24: + for tube in range(24): + p1k_1.flow_rate.aspirate = VOL_SAMPLE * NUM_REPLICATE + p1k_1.flow_rate.dispense = VOL_SAMPLE * NUM_REPLICATE + p1k_1.pick_up_tip(tips_50_loc[tube]) + start = sample_1[tube] + p1k_1.aspirate(VOL_SAMPLE * NUM_REPLICATE, start) + ctx.delay(seconds=2) + for count in range(NUM_REPLICATE): + end = rxn_well[tube * 2 + count] + p1k_1.dispense(VOL_SAMPLE, end.bottom(z=2)) + ctx.delay(seconds=2) + if heating == 2: + for count in reversed(range(NUM_REPLICATE)): + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + end = rxn_well[tube * 2 + count] + p1k_1.mix(3, 45, end) + p1k_1.blow_out(end.top(z=0)) + p1k_1.drop_tip() + + for tube in range(num_sample_final - 24): + p1k_1.flow_rate.aspirate = VOL_SAMPLE * NUM_REPLICATE + p1k_1.flow_rate.dispense = VOL_SAMPLE * NUM_REPLICATE + p1k_1.pick_up_tip(tips_50_loc[24 + tube]) + start = sample_2[tube] + p1k_1.aspirate(VOL_SAMPLE * NUM_REPLICATE, start) + ctx.delay(seconds=2) + for count in range(NUM_REPLICATE): + end = rxn_well[48 + tube * 2 + count] + p1k_1.dispense(VOL_SAMPLE, end.bottom(z=2)) + ctx.delay(seconds=2) + if heating == 2: + for count in reversed(range(NUM_REPLICATE)): + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + end = rxn_well[48 + tube * 2 + count] + p1k_1.mix(3, 45, end) + p1k_1.blow_out(end.top(z=0)) + p1k_1.drop_tip() + + else: + for tube in range(num_sample_final): + p1k_1.flow_rate.aspirate = VOL_SAMPLE * NUM_REPLICATE + p1k_1.flow_rate.dispense = VOL_SAMPLE * NUM_REPLICATE + p1k_1.pick_up_tip(tips_50_loc[tube]) + start = sample_1[tube] + p1k_1.aspirate(VOL_SAMPLE * NUM_REPLICATE, start) + ctx.delay(seconds=2) + for count in range(NUM_REPLICATE): + end = rxn_well[tube * 2 + count] + p1k_1.dispense(VOL_SAMPLE, end.bottom(z=2)) + ctx.delay(seconds=2) + if heating == 2: + for count in reversed(range(NUM_REPLICATE)): + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + end = rxn_well[tube * 2 + count] + p1k_1.mix(3, 45, end) + p1k_1.blow_out(end.top(z=0)) + p1k_1.drop_tip() + + if heating == 1: + hs.open_labware_latch() + ctx.move_labware( + labware=working_plate, + new_location=hs_adapter, + use_gripper=True, + ) + hs.close_labware_latch() + hs.set_and_wait_for_shake_speed(rpm=1250) + ctx.delay(seconds=30) + hs.deactivate_shaker() + hs.open_labware_latch() + ctx.move_labware( + labware=working_plate, + new_location="C2", + use_gripper=True, + ) + + del ctx.deck["C2"] + ctx.move_labware( + labware=working_plate_lid, + new_location="C2", + use_gripper=True, + pick_up_offset={"x": 0, "y": 0, "z": -4}, + drop_offset={"x": 0, "y": 0, "z": 3}, + ) + del ctx.deck["C2"] + working_plate = ctx.load_labware("corning_96_wellplate_360ul_flat", "C2", "WORKING PLATE") + + ## incubation + if heating == 1: + hs.set_and_wait_for_temperature(37) + heat_pad = hs_adapter + h = -8 + elif heating == 2: + temp.set_temperature(37) + heat_pad = temp_adapter + h = -10 + + ctx.move_labware( + labware=working_plate, + new_location=heat_pad, + use_gripper=True, + pick_up_offset={"x": 0, "y": 0, "z": -7}, + drop_offset={"x": 0, "y": 0, "z": h}, + ) + ctx.delay(minutes=time_incubation) + if heating == 1: + hs.deactivate_heater() + elif heating == 2: + temp.deactivate() + + ctx.pause("Measure absorbance at 480 nm on a plate reader") diff --git a/app-testing/files/protocols/pl_BacteriaInoculation_Flex_6plates.py b/app-testing/files/protocols/pl_BacteriaInoculation_Flex_6plates.py new file mode 100644 index 00000000000..a97b729d9c3 --- /dev/null +++ b/app-testing/files/protocols/pl_BacteriaInoculation_Flex_6plates.py @@ -0,0 +1,218 @@ +def get_values(*names): + import json + + _all_values = json.loads("""{"DILUTION_PREP_ON_DECK":1,"NUM_SAMPLES":6,"protocol_filename":"BacteriaInoculation_Flex_6plates"}""") + return [_all_values[n] for n in names] + + +from opentrons import types + +metadata = { + "protocolName": "Bacteria Inoculation for Colony-Forming Unit Measurement on Flex - up to 6 culture plates", + "author": "Boren Lin, Opentrons", + "source": "", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + +NUM_SAMPLES = 6 +## max. 6 samples and 6 plates +## from each sample, 6 10-fold dilutions are prepared plus 2 negative controls (medium only) +## on each plate, 1 set of above (6 dilutions + 2 controls) is inoculated + +DILUTION_PREP_ON_DECK = 1 +## Yes: 1; No: 0 + +SLOT_ADAPTER = "C2" +SLOT_STACKE = "B3" +SLOT_RESTACK = "D3" +VOL_INOCULATION = 10 +TIME_SPREAD = 25 + + +def run(ctx): + + global DILUTION_PREP_ON_DECK + global NUM_SAMPLES + + try: + [NUM_SAMPLES, DILUTION_PREP_ON_DECK] = get_values("NUM_SAMPLES", "DILUTION_PREP_ON_DECK") + except NameError: + # get_values is not defined + pass + + NUM_SAMPLES = int(NUM_SAMPLES) + DILUTION_PREP_ON_DECK = int(DILUTION_PREP_ON_DECK) + + # deck layout + if DILUTION_PREP_ON_DECK == 1: + sample_prep_stock = ctx.load_labware("opentrons_10_tuberack_nest_4x50ml_6x15ml_conical", "C1", "SAMPLES & MEDIUM") + culture_stock = sample_prep_stock.wells()[:6] + medium_stock = sample_prep_stock.wells()[9] + + hs = ctx.load_module("heaterShakerModuleV1", "D1") + sample_prep_dilutions = hs.load_labware("nest_96_wellplate_2ml_deep", "DILUTION PLATE") + dilution_plate = sample_prep_dilutions.rows()[:8][:12] + + ctx.load_trash_bin("A3") + + # liquids + if DILUTION_PREP_ON_DECK == 1: + medium_def = ctx.define_liquid( + name="MEDIUM", description="Bacterial culture medium (e.g., LB broth)", display_color="#704848" + ) ## Brown + sample_prep_stock.wells()[9].load_liquid(liquid=medium_def, volume=25000) + + sample_def = ctx.define_liquid(name="SAMPLES", description="Samples to be assayed, per tube", display_color="#FF0000") ## Red + for count in range(NUM_SAMPLES): + sample_prep_stock.wells()[count].load_liquid(liquid=sample_def, volume=1000 / NUM_SAMPLES) + else: + dilution_def = ctx.define_liquid( + name="DILUTIONS", description="Serial 10-fold dilutions of each sample, per well", display_color="#00FFF2" + ) ## Light Blue + control_def = ctx.define_liquid(name="CONTROLS", description="Controls (medium only), per well", display_color="#52AAFF") ## Blue + for count in range(NUM_SAMPLES): + sample_prep_dilutions.rows()[0][count].load_liquid(liquid=control_def, volume=450 / (NUM_SAMPLES * 2)) + sample_prep_dilutions.rows()[7][count].load_liquid(liquid=control_def, volume=450 / (NUM_SAMPLES * 2)) + for row in range(6): + sample_prep_dilutions.rows()[row + 1][count].load_liquid(liquid=dilution_def, volume=450 / (NUM_SAMPLES * 6)) + + # pipet settings + if DILUTION_PREP_ON_DECK == 1: + tiprack_1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2") + p1k_1 = ctx.load_instrument("flex_1channel_1000", "right", tip_racks=[tiprack_1000]) + else: + p1k_1 = ctx.load_instrument("flex_1channel_1000", "right") + + tiprack_50 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "B2") + p50_8 = ctx.load_instrument("flex_8channel_50", "left", tip_racks=[tiprack_50]) + + # tilt function + def tilt(slot, position): + if position == "flat": + x = 88.5 + y = 87.5 + z_upper = 150 + z_lower = 73 + p1k_1.move_to(ctx.deck.position_for(slot).move(types.Point(x=x, y=y, z=z_upper))) + p1k_1.move_to(ctx.deck.position_for(slot).move(types.Point(x=x, y=y, z=z_lower))) + + elif position == "tilt": + x = 13.5 + y = 87.5 + z_upper = 105 + z_lower = 52 + p1k_1.move_to(ctx.deck.position_for(slot).move(types.Point(x=x, y=y, z=z_upper))) + p1k_1.move_to(ctx.deck.position_for(slot).move(types.Point(x=x, y=y, z=z_lower))) + + # protocol + hs.open_labware_latch() + ctx.pause("Load the Dilution Plate on the Heater Shaker") + hs.close_labware_latch() + + ## dilution prep + if DILUTION_PREP_ON_DECK == 1: + for i in range(NUM_SAMPLES): + p1k_1.pick_up_tip() + start_medium = medium_stock + end_0 = dilution_plate[0][i] + p1k_1.aspirate(450, start_medium.bottom(z=2)) + p1k_1.air_gap(20) + p1k_1.dispense(20, end_0.top(z=2)) + p1k_1.dispense(450, end_0.top(z=-10)) + for j in range(3): + p1k_1.aspirate(900, start_medium.bottom(z=2)) + p1k_1.air_gap(20) + p1k_1.dispense(20, end_0.top(z=2)) + for k in range(2): + end = dilution_plate[(j + 1) * 2 + k][i] + p1k_1.dispense(450, end.top(z=-10)) + p1k_1.blow_out() + + start_sample = culture_stock[i] + end_1 = dilution_plate[1][i] + p1k_1.mix(5, 500, start_sample.bottom(z=5)) + p1k_1.mix(5, 500, start_sample.bottom(z=10)) + p1k_1.blow_out(start_sample.top(z=-10)) + p1k_1.aspirate(500, start_sample.bottom(z=2)) + p1k_1.air_gap(20) + p1k_1.dispense(520, end_1.top(z=-10)) + p1k_1.blow_out() + for l in range(5): + dilute_start = dilution_plate[l + 1][i] + dilute_end = dilution_plate[l + 2][i] + p1k_1.aspirate(50, dilute_start.bottom(z=2)) + p1k_1.dispense(50, dilute_end.top(z=-10)) + p1k_1.mix(5, 250, dilute_end.bottom(z=2)) + p1k_1.blow_out() + p1k_1.air_gap(20) + p1k_1.drop_tip() + + ## inoculation + for plate_count in range(NUM_SAMPLES): + working_plate = ctx.load_labware("omintray_1well_plate", SLOT_STACKE, "STACKED AGAR PLATES") + ctx.move_labware( + labware=working_plate, + new_location=SLOT_ADAPTER, + use_gripper=True, + pick_up_offset={"x": 0, "y": 0, "z": 16 * (NUM_SAMPLES - 1 - plate_count) - 2}, + drop_offset={"x": 0, "y": 0, "z": 59}, + ) + + ctx.move_labware( + labware=working_plate, + new_location="C3", + use_gripper=True, + pick_up_offset={"x": 0, "y": 0, "z": 67}, + drop_offset={"x": 0, "y": 0, "z": 0}, + ) + + tilt_plate = ctx.load_labware("omni_plate_tilt_adapter", SLOT_ADAPTER, "TILT ADAPTER") + working_plate_tilt = tilt_plate.rows()[:8][:12] + + hs.set_and_wait_for_shake_speed(2000) + ctx.delay(seconds=3) + hs.deactivate_shaker() + start = dilution_plate[0][plate_count] + end = working_plate_tilt[0][11] + p50_8.pick_up_tip() + p50_8.mix(2, 40, start.bottom(z=2)) + p50_8.aspirate(VOL_INOCULATION, start.bottom(z=2)) + ctx.delay(seconds=3) + p50_8.air_gap(5) + p50_8.dispense(5, end.top(z=0)) + p50_8.dispense(VOL_INOCULATION, end.top(z=-5)) + ctx.delay(seconds=3) + p50_8.air_gap(10) + p50_8.drop_tip() + + tilt(SLOT_ADAPTER, "tilt") + ctx.delay(seconds=TIME_SPREAD) + tilt(SLOT_ADAPTER, "flat") + ctx.delay(seconds=10) + + del ctx.deck[SLOT_ADAPTER] + + ctx.move_labware( + labware=working_plate, + new_location=SLOT_ADAPTER, + use_gripper=True, + pick_up_offset={"x": 0, "y": 0, "z": 0}, + drop_offset={"x": 0, "y": 0, "z": 68}, + ) + + ctx.move_labware( + labware=working_plate, + new_location=SLOT_RESTACK, + use_gripper=True, + pick_up_offset={"x": 0, "y": 0, "z": 57}, + drop_offset={"x": 0, "y": 0, "z": 16 * plate_count - 2}, + ) + + del ctx.deck[SLOT_RESTACK] + + hs.open_labware_latch() + ctx.pause("Move Plates to Incubator") diff --git a/app-testing/files/protocols/pl_Bradford_proteinassay.py b/app-testing/files/protocols/pl_Bradford_proteinassay.py new file mode 100644 index 00000000000..1ed10894e5d --- /dev/null +++ b/app-testing/files/protocols/pl_Bradford_proteinassay.py @@ -0,0 +1,475 @@ +metadata = {"protocolName": "Pierce Bradford Protein Assay", "author": "Boren Lin, Opentrons", "description": ""} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.18", +} + +######################## + +NUM_REPLICATE = 2 + + +def add_parameters(parameters): + parameters.add_int( + variable_name="dilution_on_deck", + display_name="Samples diluted on deck", + description="Prepare sample dilutions (2-fold serial dilutions: 1x, 0.5x, 0.25x, 0.125x)?", + default=0, + choices=[{"display_name": "Yes", "value": 1}, {"display_name": "No", "value": 0}], + ) + parameters.add_int( + variable_name="hs_on_deck", + display_name="Heater-shaker on deck", + description="Heater-shaker Module on deck for agitation?", + default=1, + choices=[{"display_name": "Yes", "value": 1}, {"display_name": "No", "value": 0}], + ) + parameters.add_int( + variable_name="time_incubation", + display_name="Incubation Time", + description="Color development - incubation for how long (min)?", + default=10, + minimum=0, + maximum=120, + ) + parameters.add_int( + variable_name="vol_sample", + display_name="Sample volume", + description="Sample volume to be assayed", + default=10, + choices=[{"display_name": "10 uL", "value": 10}, {"display_name": "150 uL", "value": 150}], + ) + parameters.add_int( + variable_name="num_sample", + display_name="Number of Samples", + description="Number of samples to be assayed. If samples need to be diluted, maximum: 12", + default=48, + minimum=1, + maximum=48, + ) + parameters.add_int( + variable_name="pipet_location", + display_name="P1000 1-ch Position", + description="How P1000 single channel pipette is mounted?", + default=1, + choices=[{"display_name": "on the right", "value": 1}, {"display_name": "on the left", "value": 2}], + ) + + +def run(ctx): + + global dilution_on_deck + global hs_on_deck + global time_incubation + global vol_sample + global num_sample + global pipet_location + + global num_sample_initial + global num_sample_final + global num_col + global vol_wr_per_rxn + global vol_wr_well_1 + global vol_wr_well_2 + + dilution_on_deck = ctx.params.dilution_on_deck + hs_on_deck = ctx.params.hs_on_deck + time_incubation = ctx.params.time_incubation + vol_sample = ctx.params.vol_sample + num_sample = ctx.params.num_sample + pipet_location = ctx.params.pipet_location + + if dilution_on_deck == 0: + num_sample_final = num_sample + ## standard + unknowns, max. 48 + + else: + num_sample_initial = num_sample + num_sample_final = num_sample * 4 + ## standard + unknowns, max. 12 + + if num_sample_final * NUM_REPLICATE > 96 or num_sample_final == 0: + raise Exception("Invalid sample number") + + num_rxn = num_sample_final * NUM_REPLICATE + num_col = int(num_rxn // 8) + if num_rxn % 8 != 0: + num_col = num_col + 1 + + if vol_sample == 10: + vol_wr_per_rxn = 250 + elif vol_sample == 150: + vol_wr_per_rxn = 150 + + if num_col > 6: + vol_wr_well_1 = (6 - 1) * vol_wr_per_rxn * 8 + 2000 + vol_wr_well_2 = (num_col - 6 - 1) * vol_wr_per_rxn * 8 + 2000 + + else: + vol_wr_well_1 = (num_col - 1) * vol_wr_per_rxn * 8 + 2000 + vol_wr_well_2 = 0 + + if pipet_location == 1: + p1k_1_loc = "right" + p1k_8_loc = "left" + else: + p1k_1_loc = "left" + p1k_8_loc = "right" + + # deck layout + if hs_on_deck == 1: + hs = ctx.load_module("heaterShakerModuleV1", "D1") + hs_adapter = hs.load_adapter("opentrons_universal_flat_adapter") + + if dilution_on_deck == 1: + reagent_stock_rack = ctx.load_labware("opentrons_10_tuberack_nest_4x50ml_6x15ml_conical", "A2", "BUFFER") + + wr_reservoir = ctx.load_labware("nest_12_reservoir_15ml", "B2", "WORKING REAGENT") + sample_rack_1 = ctx.load_labware("opentrons_24_tuberack_nest_1.5ml_snapcap", "C1", "SAMPLES") + if num_sample_final > 24: + sample_rack_2 = ctx.load_labware("opentrons_24_tuberack_nest_1.5ml_snapcap", "B1", "SAMPLES") + + ctx.load_trash_bin("A3") + + if dilution_on_deck == 1: + if vol_sample == 10: + tips_200_dilute = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A1", "P200 TIPS") + tips_dilute_loc = tips_200_dilute.wells()[:96] + elif vol_sample == 150: + tips_1000_dilute = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "P1000 TIPS") + tips_dilute_loc = tips_1000_dilute.wells()[:96] + + tips_1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B3", "P1000 TIPS") + tips_1000_loc = tips_1000.wells()[:96] + + if vol_sample == 10: + tips_50_sample = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "C3", "P50 TIPS") + tips_sample_loc = tips_50_sample.wells()[:96] + elif vol_sample == 150: + tips_1000_sample = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "C3", "P1000 TIPS") + tips_sample_loc = tips_1000_sample.wells()[:96] + + p1k_1 = ctx.load_instrument("flex_1channel_1000", p1k_1_loc) + p1k_8 = ctx.load_instrument("flex_8channel_1000", p1k_8_loc) + + # liquid location + if dilution_on_deck == 1: + buffer = reagent_stock_rack.wells()[6] + + wr_1 = wr_reservoir.wells()[0] + if num_col > 6: + wr_2 = wr_reservoir.wells()[1] + sample_1 = sample_rack_1.wells()[:24] + if num_sample_final > 24: + sample_2 = sample_rack_2.wells()[:24] + + # generate desk layout and volume info + if num_col > 6: + vol_1 = (6 - 1) * vol_wr_per_rxn * 8 + 3000 + vol_2 = (num_col - 6 - 1) * vol_wr_per_rxn * 8 + 3000 + def_1 = ctx.define_liquid( + name="WORKING REAGENT", description="Coomassie Brilliant Blue G-250 solution ", display_color="#0000FF" + ) ## Blue + wr_reservoir.wells()[0].load_liquid(liquid=def_1, volume=vol_1) + def_2 = ctx.define_liquid( + name="WORKING REAGENT", description="Coomassie Brilliant Blue G-250 solution ", display_color="#0000FF" + ) ## Blue + wr_reservoir.wells()[1].load_liquid(liquid=def_2, volume=vol_2) + else: + vol_1 = (num_col - 1) * vol_wr_per_rxn * 8 + 3000 + def_1 = ctx.define_liquid( + name="WORKING REAGENT", description="Coomassie Brilliant Blue G-250 solution ", display_color="#0000FF" + ) ## Blue + wr_reservoir.wells()[0].load_liquid(liquid=def_1, volume=vol_1) + + if dilution_on_deck == 1: + vol_undiluted = (vol_sample + 5) * NUM_REPLICATE * 2 + 10 + vol_buffer = (vol_sample + 5) * NUM_REPLICATE * 3 * num_sample_initial + 50 + if num_sample_initial > 6: + def_undiluted_1 = ctx.define_liquid( + name="SAMPLES", description="Undiluted Samples, volume per tube (Slot C1)", display_color="#FF0000" + ) ## Red + def_undiluted_2 = ctx.define_liquid( + name="SAMPLES", description="Undiluted Samples, volume per tube (Slot B1)", display_color="#FF0000" + ) ## Red + for p in range(6): + sample_rack_1.rows()[0][p].load_liquid(liquid=def_undiluted_1, volume=vol_undiluted / 6) + for q in range(num_sample_initial - 6): + sample_rack_2.rows()[0][q].load_liquid(liquid=def_undiluted_2, volume=vol_undiluted / (num_sample_initial - 6)) + else: + def_undiluted_1 = ctx.define_liquid( + name="SAMPLES", description="Undiluted Samples, volume per tube (Slot C1)", display_color="#FF0000" + ) ## Red + for p in range(num_sample_initial): + sample_rack_1.rows()[0][p].load_liquid(liquid=def_undiluted_1, volume=vol_undiluted / num_sample_initial) + def_buffer = ctx.define_liquid(name="BUFFER", description="Buffer for sample dilution", display_color="#704848") ## Brown + reagent_stock_rack.wells()[6].load_liquid(liquid=def_buffer, volume=vol_buffer) + else: + vol_ = vol_sample * NUM_REPLICATE + 10 + if num_sample_final > 24: + def_1 = ctx.define_liquid(name="SAMPLES", description="Samples, volume per tube (Slot C1)", display_color="#FF0000") ## Red + def_2 = ctx.define_liquid(name="SAMPLES", description="Samples, volume per tube (Slot B1)", display_color="#FF0000") ## Red + for p in range(24): + sample_rack_1.wells()[p].load_liquid(liquid=def_1, volume=vol_ / 24) + for q in range(num_sample_final - 24): + sample_rack_2.wells()[q].load_liquid(liquid=def_2, volume=vol_ / (num_sample_final - 24)) + else: + def_1 = ctx.define_liquid(name="SAMPLES", description="Samples, volume per tube (Slot C1)", display_color="#FF0000") ## Red + for p in range(num_sample_final): + sample_rack_1.wells()[p].load_liquid(liquid=def_1, volume=vol_ / num_sample_final) + + # protocol + ## dilution prep + if dilution_on_deck == 1: + + p1k_1.pick_up_tip(tips_dilute_loc[0]) + if num_sample_initial > 6: + for x in range(6): + p1k_1.aspirate((vol_sample + 5) * NUM_REPLICATE * 3, buffer) + ctx.delay(seconds=2) + for y in range(3): + end = sample_1[y + x * 4 + 1] + p1k_1.flow_rate.dispense = vol_sample * NUM_REPLICATE + p1k_1.dispense((vol_sample + 5) * NUM_REPLICATE, end.bottom(z=10)) + ctx.delay(seconds=2) + p1k_1.blow_out(buffer.top(z=0)) + for x in range(num_sample_initial - 6): + p1k_1.aspirate((vol_sample + 5) * NUM_REPLICATE * 3, buffer) + ctx.delay(seconds=2) + for y in range(3): + end = sample_2[y + x * 4 + 1] + p1k_1.flow_rate.dispense = vol_sample * NUM_REPLICATE + p1k_1.dispense((vol_sample + 5) * NUM_REPLICATE, end.bottom(z=10)) + ctx.delay(seconds=2) + else: + for x in range(num_sample_initial): + p1k_1.aspirate((vol_sample + 5) * NUM_REPLICATE * 3, buffer) + ctx.delay(seconds=2) + for y in range(3): + end = sample_1[y + x * 4 + 1] + p1k_1.flow_rate.dispense = vol_sample * NUM_REPLICATE + p1k_1.dispense((vol_sample + 5) * NUM_REPLICATE, end.bottom(z=10)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + + if num_sample_initial > 6: + p1k_1.flow_rate.aspirate = vol_sample * NUM_REPLICATE + for x in range(6): + p1k_1.pick_up_tip(tips_dilute_loc[x + 1]) + for y in range(3): + start = sample_1[x * 4 + y] + end = sample_1[x * 4 + y + 1] + p1k_1.aspirate((vol_sample + 5) * NUM_REPLICATE, start) + p1k_1.dispense((vol_sample + 5) * NUM_REPLICATE, end.bottom(z=10)) + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + p1k_1.mix(3, vol_sample, end) + p1k_1.blow_out(end.top(z=-5)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + for x in range(num_sample_initial - 6): + p1k_1.pick_up_tip(tips_dilute_loc[x + 7]) + for y in range(3): + start = sample_2[x * 4 + y] + end = sample_2[x * 4 + y + 1] + p1k_1.aspirate((vol_sample + 5) * NUM_REPLICATE, start) + p1k_1.dispense((vol_sample + 5) * NUM_REPLICATE, end.bottom(z=10)) + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + p1k_1.mix(3, vol_sample, end) + p1k_1.blow_out(end.top(z=-5)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + else: + p1k_1.flow_rate.aspirate = vol_sample * NUM_REPLICATE + for x in range(num_sample_initial): + p1k_1.pick_up_tip(tips_dilute_loc[x + 1]) + for y in range(3): + start = sample_1[x * 4 + y] + end = sample_1[x * 4 + y + 1] + p1k_1.aspirate((vol_sample + 5) * NUM_REPLICATE, start) + p1k_1.dispense((vol_sample + 5) * NUM_REPLICATE, end.bottom(z=10)) + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + p1k_1.mix(3, vol_sample, end) + p1k_1.blow_out(end.top(z=-5)) + ctx.delay(seconds=2) + p1k_1.drop_tip() + + ## working plate filling + working_plate = ctx.load_labware("corning_96_wellplate_360ul_flat", "C2", "WORKING PLATE") + working_plate_lid = ctx.load_labware("corning_96_wellplate_360ul_flat", "D2", "PLATE LID") + rxn_col = working_plate.rows()[0][:12] + rxn_well = working_plate.wells()[:96] + + ### add working reagent + p1k_8.pick_up_tip(tips_1000_loc[0]) + if num_col > 6: + for i in range(2): + p1k_8.aspirate(vol_wr_per_rxn * 3, wr_1) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[i * 3] + p1k_8.dispense(20, end.top(z=0)) + for j in range(3): + end = rxn_col[i * 3 + j] + p1k_8.dispense(vol_wr_per_rxn, end.top(z=0)) + ctx.delay(seconds=2) + col = num_col - 6 + if col > 3: + p1k_8.aspirate(vol_wr_per_rxn * 3, wr_2) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[6] + p1k_8.dispense(20, end.top(z=0)) + for jj in range(3): + end = rxn_col[6 + jj] + p1k_8.dispense(vol_wr_per_rxn, end.top(z=0)) + ctx.delay(seconds=2) + ii = col - 3 + p1k_8.aspirate(vol_wr_per_rxn * ii, wr_2) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[9] + p1k_8.dispense(20, end.top(z=0)) + for iii in range(ii): + end = rxn_col[9 + iii] + p1k_8.dispense(vol_wr_per_rxn, end.top(z=0)) + ctx.delay(seconds=2) + else: + p1k_8.aspirate(vol_wr_per_rxn * col, wr_2) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[6] + p1k_8.dispense(20, end.top(z=0)) + for jjj in range(col): + end = rxn_col[6 + jjj] + p1k_8.dispense(vol_wr_per_rxn, end.top(z=0)) + ctx.delay(seconds=2) + else: + if num_col > 3: + p1k_8.aspirate(vol_wr_per_rxn * 3, wr_1) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[0] + p1k_8.dispense(20, end.top(z=0)) + for g in range(3): + end = rxn_col[g] + p1k_8.dispense(vol_wr_per_rxn, end.top(z=0)) + ctx.delay(seconds=2) + col = num_col - 3 + p1k_8.aspirate(vol_wr_per_rxn * col, wr_1) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[3] + p1k_8.dispense(20, end.top(z=0)) + for h in range(col): + end = rxn_col[3 + h] + p1k_8.dispense(vol_wr_per_rxn, end.top(z=0)) + ctx.delay(seconds=2) + else: + p1k_8.aspirate(vol_wr_per_rxn * num_col, wr_1) + ctx.delay(seconds=2) + p1k_8.air_gap(20) + end = rxn_col[0] + p1k_8.dispense(20, end.top(z=0)) + for hh in range(num_col): + end = rxn_col[hh] + p1k_8.dispense(vol_wr_per_rxn, end.top(z=0)) + ctx.delay(seconds=2) + + p1k_8.drop_tip() + + ### add samples + if num_sample_final > 24: + for tube in range(24): + p1k_1.flow_rate.aspirate = vol_sample * NUM_REPLICATE + p1k_1.flow_rate.dispense = vol_sample * NUM_REPLICATE + p1k_1.pick_up_tip(tips_sample_loc[tube]) + start = sample_1[tube] + p1k_1.aspirate(vol_sample * NUM_REPLICATE, start) + ctx.delay(seconds=2) + for count in range(NUM_REPLICATE): + end = rxn_well[tube * 2 + count] + p1k_1.dispense(vol_sample, end.bottom(z=2)) + ctx.delay(seconds=2) + if hs_on_deck == 0: + for count in reversed(range(NUM_REPLICATE)): + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + end = rxn_well[tube * 2 + count] + p1k_1.mix(3, 45, end) + p1k_1.blow_out(end.top(z=0)) + p1k_1.drop_tip() + + for tube in range(num_sample_final - 24): + p1k_1.flow_rate.aspirate = vol_sample * NUM_REPLICATE + p1k_1.flow_rate.dispense = vol_sample * NUM_REPLICATE + p1k_1.pick_up_tip(tips_sample_loc[24 + tube]) + start = sample_2[tube] + p1k_1.aspirate(vol_sample * NUM_REPLICATE, start) + ctx.delay(seconds=2) + for count in range(NUM_REPLICATE): + end = rxn_well[48 + tube * 2 + count] + p1k_1.dispense(vol_sample, end.bottom(z=2)) + ctx.delay(seconds=2) + if hs_on_deck == 0: + for count in reversed(range(NUM_REPLICATE)): + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + end = rxn_well[48 + tube * 2 + count] + p1k_1.mix(3, 45, end) + p1k_1.blow_out(end.top(z=0)) + p1k_1.drop_tip() + + else: + for tube in range(num_sample_final): + p1k_1.flow_rate.aspirate = vol_sample * NUM_REPLICATE + p1k_1.flow_rate.dispense = vol_sample * NUM_REPLICATE + p1k_1.pick_up_tip(tips_sample_loc[tube]) + start = sample_1[tube] + p1k_1.aspirate(vol_sample * NUM_REPLICATE, start) + ctx.delay(seconds=2) + for count in range(NUM_REPLICATE): + end = rxn_well[tube * 2 + count] + p1k_1.dispense(vol_sample, end.bottom(z=2)) + ctx.delay(seconds=2) + if hs_on_deck == 0: + for count in reversed(range(NUM_REPLICATE)): + p1k_1.flow_rate.aspirate = 200 + p1k_1.flow_rate.dispense = 200 + end = rxn_well[tube * 2 + count] + p1k_1.mix(3, 45, end) + p1k_1.blow_out(end.top(z=0)) + p1k_1.drop_tip() + + if hs_on_deck == 1: + hs.open_labware_latch() + ctx.move_labware( + labware=working_plate, + new_location=hs_adapter, + use_gripper=True, + ) + hs.close_labware_latch() + hs.set_and_wait_for_shake_speed(rpm=1250) + ctx.delay(seconds=30) + hs.deactivate_shaker() + hs.open_labware_latch() + ctx.move_labware( + labware=working_plate, + new_location="C2", + use_gripper=True, + ) + + del ctx.deck["C2"] + ctx.move_labware( + labware=working_plate_lid, + new_location="C2", + use_gripper=True, + pick_up_offset={"x": 0, "y": 0, "z": -4}, + drop_offset={"x": 0, "y": 0, "z": 3}, + ) + + ctx.delay(minutes=time_incubation) + ctx.pause("Measure absorbance at 595 nm on a plate reader") diff --git a/app-testing/files/protocols/pl_Dynabeads_IP_Flex_96well_RIT_final.py b/app-testing/files/protocols/pl_Dynabeads_IP_Flex_96well_RIT_final.py new file mode 100644 index 00000000000..4137555a653 --- /dev/null +++ b/app-testing/files/protocols/pl_Dynabeads_IP_Flex_96well_RIT_final.py @@ -0,0 +1,316 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"NUM_COL":12,"INCUBATION_ON_DECK":1,"INCUBATION_MIN":60,"READY_FOR_SDSPAGE":1,"protocol_filename":"Dynabeads_IP_Flex_96well_RIT_final"}""" + ) + return [_all_values[n] for n in names] + + +metadata = { + "protocolName": "Immunoprecipitation by Dynabeads - 96-well setting on Opentrons Flex (Reagents in 15 mL tubes)", + "author": "Boren Lin, Opentrons", + "description": "The protocol automates immunoprecipitation to isolate a protein of interest from liquid samples (up to 96 samples) by using protein A– or protein G–coupled magnetic beads.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.15", +} + +######################## + +NUM_COL = 12 +INCUBATION_ON_DECK = 1 # Yes:1; No:0 +INCUBATION_MIN = 60 +READY_FOR_SDSPAGE = 0 # YES:1; NO:0 + +# if on deck: +INCUBATION_SPEEND = 1000 + +ASP_HEIGHT = 0.2 +MIX_SPEEND = 2000 +MIX_SEC = 10 + +MAG_DELAY_MIN = 1 + +BEADS_VOL = 50 +AB_VOL = 50 +SAMPLE_VOL = 200 +WASH_TIMES = 3 +WASH_VOL = 200 +ELUTION_VOL = 50 + +WASTE_VOL_MAX = 275000 + +USE_GRIPPER = True + +waste_vol_chk = 0 + +######################### + + +def run(ctx): + + global NUM_COL + global INCUBATION_ON_DECK + global INCUBATION_MIN + global READY_FOR_SDSPAGE + + try: + [NUM_COL, INCUBATION_ON_DECK, INCUBATION_MIN, READY_FOR_SDSPAGE] = get_values( + "NUM_COL", "INCUBATION_ON_DECK", "INCUBATION_MIN", "READY_FOR_SDSPAGE" + ) + except NameError: + # get_values is not defined + pass + + NUM_COL = int(NUM_COL) + INCUBATION_ON_DECK = int(INCUBATION_ON_DECK) + INCUBATION_MIN = int(INCUBATION_MIN) + READY_FOR_SDSPAGE = int(READY_FOR_SDSPAGE) + + if READY_FOR_SDSPAGE == 0 and NUM_COL > 6: + USE_TRASH_BIN = 0 + else: + USE_TRASH_BIN = 1 + + # load labware + sample_plate = ctx.load_labware("nest_96_wellplate_2ml_deep", "B2", "samples") + wash_res = ctx.load_labware("nest_12_reservoir_15ml", "B1", "wash") + reagent_res = ctx.load_labware("opentrons_15_tuberack_nest_15ml_conical", "C3", "reagents") + waste_res = ctx.load_labware("nest_1_reservoir_290ml", "D2", "waste") + + tips = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B3") + tips_sample = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "sample tips") + tips_sample_loc = tips_sample.wells()[:95] + if READY_FOR_SDSPAGE == 0: + tips_elu = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "elution tips") + tips_elu_loc = tips_elu.wells()[:95] + tips_reused = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "C2", "reused tips") + tips_reused_loc = tips_reused.wells()[:95] + p1000 = ctx.load_instrument("flex_8channel_1000", "left", tip_racks=[tips]) + p1000_single = ctx.load_instrument("flex_1channel_1000", "right", tip_racks=[tips]) + default_rate = 700 + p1000.flow_rate.aspirate = default_rate + p1000.flow_rate.dispense = default_rate + p1000_single.flow_rate.aspirate = default_rate + p1000_single.flow_rate.dispense = default_rate + + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + working_plate = h_s_adapter.load_labware("nest_96_wellplate_2ml_deep", "working plate") + + if READY_FOR_SDSPAGE == 0: + temp = ctx.load_module("Temperature Module Gen2", "D3") + final_plate = temp.load_labware("opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep", "final plate") + + mag = ctx.load_module("magneticBlockV1", "C1") + + # liquids + beads_vol_stock = (NUM_COL * 8 + 2) * BEADS_VOL + beads_def = ctx.define_liquid(name="Beads", description="Dynebeads Slurry", display_color="#704848") ## Brown + reagent_res.wells()[0].load_liquid(liquid=beads_def, volume=beads_vol_stock) + + ab_vol_stock = (NUM_COL * 8 + 2) * AB_VOL + ab_def = ctx.define_liquid(name="Antibody", description="Antibody in Solution", display_color="#9ACECB") ## Blue + reagent_res.wells()[1].load_liquid(liquid=ab_def, volume=ab_vol_stock) + + elu_vol_stock = (NUM_COL * 8 + 2) * ELUTION_VOL + elu_def = ctx.define_liquid(name="Elution", description="Elution Buffer", display_color="#00FFF2") ## Light Blue + reagent_res.wells()[2].load_liquid(liquid=elu_def, volume=elu_vol_stock) + + sample_vol_stock = SAMPLE_VOL + 50 + sample_def = ctx.define_liquid(name="Samples", description="Sample per well", display_color="#52AAFF") ## Blue + for well_count in range(NUM_COL * 8): + sample_plate.wells()[well_count].load_liquid(liquid=sample_def, volume=sample_vol_stock / (NUM_COL * 8)) + + wash_vol_stock = WASH_VOL * (8 + 2) * 3 + wash_def = ctx.define_liquid(name="Wash", description="Wash Buffer per well", display_color="#FF0000") ## Red + for col_count in range(NUM_COL): + wash_res.wells()[col_count].load_liquid(liquid=wash_def, volume=wash_vol_stock / (NUM_COL)) + + samples = sample_plate.rows()[0][:NUM_COL] ## 1 + beads = reagent_res.wells()[0] ## 2 + ab = reagent_res.wells()[1] ## 3 + elu = reagent_res.wells()[2] ## 4 + wash = wash_res.rows()[0][:NUM_COL] ## 5 + waste = waste_res.wells()[0] + working_cols = working_plate.rows()[0][:NUM_COL] ## 6 + working_wells = working_plate.wells()[: NUM_COL * 8] ## 6 + if READY_FOR_SDSPAGE == 0: + final_cols = final_plate.rows()[0][:NUM_COL] + + def transfer_plate_to_plate(vol1, start, end, liquid, drop_height=-20): + for i in range(NUM_COL): + if liquid == 1: + p1000.pick_up_tip(tips_sample_loc[i * 8]) + else: + p1000.pick_up_tip(tips_elu_loc[i * 8]) + p1000.flow_rate.aspirate = 50 + start_loc = start[i] + end_loc = end[i] + p1000.aspirate(vol1, start_loc.bottom(z=ASP_HEIGHT)) + p1000.air_gap(10) + p1000.dispense(vol1 + 10, end_loc.top(z=drop_height)) + p1000.blow_out() + p1000.touch_tip() + if USE_TRASH_BIN == 1: + p1000.drop_tip() + else: + p1000.return_tip() + + def transfer_well_to_plate(vol2, start, end, liquid, drop_height=-20): + if liquid == 5: + p1000.pick_up_tip() + for j in range(NUM_COL): + start_loc = start[j] + end_loc = end[j] + p1000.aspirate(vol2, start_loc.bottom(z=ASP_HEIGHT)) + p1000.air_gap(10) + p1000.dispense(vol2 + 10, end_loc.top(z=drop_height)) + p1000.blow_out() + p1000.drop_tip() + else: + p1000_single.pick_up_tip() + start_loc = start + vol = vol2 * 8 + p1000_single.mix(5, vol * 0.75, start_loc.bottom(z=ASP_HEIGHT * 5)) + p1000_single.mix(5, vol * 0.75, start_loc.bottom(z=ASP_HEIGHT * 20)) + for j in range(NUM_COL): + end_loc_gap = end[j * 8] + if liquid == 2: + p1000_single.mix(2, vol * 0.75, start_loc.bottom(z=ASP_HEIGHT * 5)) + p1000_single.aspirate(vol, start_loc.bottom(z=ASP_HEIGHT * 5)) + p1000_single.air_gap(10) + p1000_single.dispense(10, end_loc_gap.top(z=-5)) + for jj in range(8): + end_loc = end[j * 8 + jj] + p1000_single.flow_rate.dispense = 500 + p1000_single.dispense(vol2, end_loc.bottom(z=10), rate=0.75) + p1000_single.flow_rate.dispense = default_rate + p1000_single.touch_tip() + p1000_single.blow_out() + p1000_single.drop_tip() + + def mix(speend, time): + ctx.comment("\n\n\n~~~~~~~~Shake to mix~~~~~~~~\n") + h_s.set_and_wait_for_shake_speed(rpm=speend) + ctx.delay(seconds=time) + h_s.deactivate_shaker() + + def discard(vol3, start): + global waste_vol + global waste_vol_chk + if waste_vol_chk >= WASTE_VOL_MAX: + ctx.pause("Empty Liquid Waste") + waste_vol_chk = 0 + waste_vol = 0 + for k in range(NUM_COL): + p1000.pick_up_tip(tips_reused_loc[k * 8]) + start_loc = start[k] + end_loc = waste + p1000.flow_rate.aspirate = 100 + p1000.aspirate(vol3, start_loc.bottom(z=ASP_HEIGHT)) + p1000.flow_rate.aspirate = default_rate + p1000.air_gap(10) + p1000.dispense(vol3 + 10, end_loc.top(z=-5)) + p1000.blow_out() + p1000.return_tip() + waste_vol = vol3 * NUM_COL * 8 + waste_vol_chk = waste_vol_chk + waste_vol + + # protocol + + ## Add beads, samples and antibody solution + h_s.open_labware_latch() + ctx.pause("Move the Working Plate to the Shaker") + h_s.close_labware_latch() + + transfer_well_to_plate(BEADS_VOL, beads, working_wells, 2) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + discard(BEADS_VOL * 1.1, working_cols) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer_plate_to_plate(SAMPLE_VOL, samples, working_cols, 1) + transfer_well_to_plate(AB_VOL, ab, working_wells, 3) + + h_s.set_and_wait_for_shake_speed(rpm=MIX_SPEEND) + ctx.delay(seconds=MIX_SEC) + + if INCUBATION_ON_DECK == 1: + h_s.set_and_wait_for_shake_speed(rpm=INCUBATION_SPEEND) + ctx.delay(seconds=INCUBATION_MIN * 60) + h_s.deactivate_shaker() + h_s.open_labware_latch() + + else: + # incubation off deck + h_s.deactivate_shaker() + h_s.open_labware_latch() + ctx.pause("Seal the Plate") + ctx.pause("Remove the Seal, Move the Plate to Shaker") + + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + ctx.delay(minutes=MAG_DELAY_MIN) + vol_total = SAMPLE_VOL + AB_VOL + discard(vol_total * 1.1, working_cols) + + ## Wash + for _ in range(WASH_TIMES): + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer_well_to_plate(WASH_VOL, wash, working_cols, 5) + mix(MIX_SPEEND, MIX_SEC) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + discard(WASH_VOL * 1.1, working_cols) + + ## Elution + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer_well_to_plate(ELUTION_VOL, elu, working_wells, 4) + if READY_FOR_SDSPAGE == 1: + ctx.pause("Seal the Working Plate") + h_s.set_and_wait_for_temperature(70) + mix(MIX_SPEEND, MIX_SEC) + ctx.delay(minutes=10) + h_s.deactivate_heater() + h_s.open_labware_latch() + ctx.pause("Protocol Complete") + + elif READY_FOR_SDSPAGE == 0: + mix(MIX_SPEEND, MIX_SEC) + ctx.delay(minutes=2) + temp.set_temperature(4) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + transfer_plate_to_plate(ELUTION_VOL * 1.1, working_cols, final_cols, 6, -5) + ctx.pause("Protocol Complete") + temp.deactivate() diff --git a/app-testing/files/protocols/pl_Dynabeads_IP_Flex_96well_final.py b/app-testing/files/protocols/pl_Dynabeads_IP_Flex_96well_final.py new file mode 100644 index 00000000000..4152d8b34ea --- /dev/null +++ b/app-testing/files/protocols/pl_Dynabeads_IP_Flex_96well_final.py @@ -0,0 +1,308 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"NUM_COL":12,"INCUBATION_ON_DECK":1,"INCUBATION_MIN":60,"READY_FOR_SDSPAGE":1,"protocol_filename":"Dynabeads_IP_Flex_96well_final"}""" + ) + return [_all_values[n] for n in names] + + +metadata = { + "protocolName": "Immunoprecipitation by Dynabeads - 96-well setting on Opentrons Flex", + "author": "Boren Lin, Opentrons", + "description": "The protocol automates immunoprecipitation to isolate a protein of interest from liquid samples (up to 96 samples) by using protein A– or protein G–coupled magnetic beads.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.15", +} + +######################## + +NUM_COL = 12 +INCUBATION_ON_DECK = 1 # Yes:1; No:0 +INCUBATION_MIN = 60 +READY_FOR_SDSPAGE = 0 # YES:1; NO:0 + +# if on deck: +INCUBATION_SPEEND = 1000 + +ASP_HEIGHT = 0.2 +MIX_SPEEND = 2000 +MIX_SEC = 10 + +MAG_DELAY_MIN = 1 + +BEADS_VOL = 50 +AB_VOL = 50 +SAMPLE_VOL = 200 +WASH_TIMES = 3 +WASH_VOL = 200 +ELUTION_VOL = 50 + +WASTE_VOL_MAX = 275000 + +USE_GRIPPER = True + +waste_vol_chk = 0 + +######################### + + +def run(ctx): + + global NUM_COL + global INCUBATION_ON_DECK + global INCUBATION_MIN + global READY_FOR_SDSPAGE + + try: + [NUM_COL, INCUBATION_ON_DECK, INCUBATION_MIN, READY_FOR_SDSPAGE] = get_values( + "NUM_COL", "INCUBATION_ON_DECK", "INCUBATION_MIN", "READY_FOR_SDSPAGE" + ) + except NameError: + # get_values is not defined + pass + + NUM_COL = int(NUM_COL) + INCUBATION_ON_DECK = int(INCUBATION_ON_DECK) + INCUBATION_MIN = int(INCUBATION_MIN) + READY_FOR_SDSPAGE = int(READY_FOR_SDSPAGE) + + if READY_FOR_SDSPAGE == 0 and NUM_COL > 6: + USE_TRASH_BIN = 0 + else: + USE_TRASH_BIN = 1 + + # load labware + + sample_plate = ctx.load_labware("nest_96_wellplate_2ml_deep", "B2", "samples") + wash_res = ctx.load_labware("nest_12_reservoir_15ml", "B1", "wash") + reagent_res = ctx.load_labware("nest_12_reservoir_15ml", "C3", "reagents") + waste_res = ctx.load_labware("nest_1_reservoir_290ml", "D2", "waste") + + tips = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B3") + tips_sample = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "sample tips") + tips_sample_loc = tips_sample.wells()[:95] + if READY_FOR_SDSPAGE == 0: + tips_elu = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "elution tips") + tips_elu_loc = tips_elu.wells()[:95] + tips_reused = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "C2", "reused tips") + tips_reused_loc = tips_reused.wells()[:95] + p1000 = ctx.load_instrument("flex_8channel_1000", "left", tip_racks=[tips]) + default_rate = 700 + p1000.flow_rate.aspirate = default_rate + p1000.flow_rate.dispense = default_rate + + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + working_plate = h_s_adapter.load_labware("nest_96_wellplate_2ml_deep", "working plate") + + if READY_FOR_SDSPAGE == 0: + temp = ctx.load_module("Temperature Module Gen2", "D3") + final_plate = temp.load_labware("opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep", "final plate") + + mag = ctx.load_module("magneticBlockV1", "C1") + + # liquids + beads_vol_stock = (NUM_COL - 1) * 8 * BEADS_VOL + 2000 + beads_def = ctx.define_liquid(name="Beads", description="Dynebeads Slurry", display_color="#704848") ## Brown + reagent_res.wells()[0].load_liquid(liquid=beads_def, volume=beads_vol_stock) + + ab_vol_stock = (NUM_COL - 1) * 8 * AB_VOL + 2000 + ab_def = ctx.define_liquid(name="Antibody", description="Antibody in Solution", display_color="#9ACECB") ## Blue + reagent_res.wells()[1].load_liquid(liquid=ab_def, volume=ab_vol_stock) + + elu_vol_stock = (NUM_COL - 1) * 8 * ELUTION_VOL + 2000 + elu_def = ctx.define_liquid(name="Elution", description="Elution Buffer", display_color="#00FFF2") ## Light Blue + reagent_res.wells()[2].load_liquid(liquid=elu_def, volume=elu_vol_stock) + + sample_vol_stock = SAMPLE_VOL + 50 + sample_def = ctx.define_liquid(name="Samples", description="Sample per well", display_color="#52AAFF") ## Blue + for well_count in range(NUM_COL * 8): + sample_plate.wells()[well_count].load_liquid(liquid=sample_def, volume=sample_vol_stock / (NUM_COL * 8)) + + wash_vol_stock = WASH_VOL * (8 + 2) * 3 + wash_def = ctx.define_liquid(name="Wash", description="Wash Buffer per well", display_color="#FF0000") ## Red + for col_count in range(NUM_COL): + wash_res.wells()[col_count].load_liquid(liquid=wash_def, volume=wash_vol_stock / (NUM_COL)) + + samples = sample_plate.rows()[0][:NUM_COL] ## 1 + beads = reagent_res.wells()[0] ## 2 + ab = reagent_res.wells()[1] ## 3 + elu = reagent_res.wells()[2] ## 4 + wash = wash_res.wells()[:NUM_COL] ## 5 + waste = waste_res.wells()[0] + working_cols = working_plate.rows()[0][:NUM_COL] ## 6 + if READY_FOR_SDSPAGE == 0: + final_cols = final_plate.rows()[0][:NUM_COL] + + def transfer_plate_to_plate(vol1, start, end, liquid, drop_height=-20): + for i in range(NUM_COL): + if liquid == 1: + p1000.pick_up_tip(tips_sample_loc[i * 8]) + else: + p1000.pick_up_tip(tips_elu_loc[i * 8]) + p1000.flow_rate.aspirate = 50 + start_loc = start[i] + end_loc = end[i] + p1000.aspirate(vol1, start_loc.bottom(z=ASP_HEIGHT)) + p1000.air_gap(10) + p1000.dispense(vol1 + 10, end_loc.top(z=drop_height)) + p1000.blow_out() + p1000.touch_tip() + if USE_TRASH_BIN == 1: + p1000.drop_tip() + else: + p1000.return_tip() + + def transfer_well_to_plate(vol2, start, end, liquid, drop_height=-20): + if liquid == 5: + p1000.pick_up_tip() + for j in range(NUM_COL): + start_loc = start[j] + end_loc = end[j] + p1000.aspirate(vol2, start_loc.bottom(z=ASP_HEIGHT)) + p1000.air_gap(10) + p1000.dispense(vol2 + 10, end_loc.top(z=drop_height)) + p1000.blow_out() + p1000.drop_tip() + else: + p1000.pick_up_tip() + start_loc = start + end_loc_gap = end[0] + p1000.mix(10, vol2 * NUM_COL * 0.5, start_loc.bottom(z=ASP_HEIGHT)) + p1000.aspirate(vol2 * NUM_COL, start_loc.bottom(z=ASP_HEIGHT)) + p1000.air_gap(10) + p1000.dispense(10, end_loc_gap.top(z=-5)) + for j in range(NUM_COL): + end_loc = end[j] + p1000.flow_rate.dispense = 500 + p1000.dispense(vol2, end_loc.top(z=drop_height)) + p1000.flow_rate.dispense = default_rate + p1000.touch_tip() + p1000.blow_out() + p1000.drop_tip() + + def mix(speend, time): + ctx.comment("\n\n\n~~~~~~~~Shake to mix~~~~~~~~\n") + h_s.set_and_wait_for_shake_speed(rpm=speend) + ctx.delay(seconds=time) + h_s.deactivate_shaker() + + def discard(vol3, start): + global waste_vol + global waste_vol_chk + if waste_vol_chk >= WASTE_VOL_MAX: + ctx.pause("Empty Liquid Waste") + waste_vol_chk = 0 + waste_vol = 0 + for k in range(NUM_COL): + p1000.pick_up_tip(tips_reused_loc[k * 8]) + start_loc = start[k] + end_loc = waste + p1000.flow_rate.aspirate = 100 + p1000.aspirate(vol3, start_loc.bottom(z=ASP_HEIGHT)) + p1000.flow_rate.aspirate = default_rate + p1000.air_gap(10) + p1000.dispense(vol3 + 10, end_loc.top(z=-5)) + p1000.blow_out() + p1000.return_tip() + waste_vol = vol3 * NUM_COL * 8 + waste_vol_chk = waste_vol_chk + waste_vol + + # protocol + + ## Add beads, samples and antibody solution + h_s.open_labware_latch() + ctx.pause("Move the Working Plate to the Shaker") + h_s.close_labware_latch() + + transfer_well_to_plate(BEADS_VOL, beads, working_cols, 2) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + discard(BEADS_VOL * 1.1, working_cols) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer_plate_to_plate(SAMPLE_VOL, samples, working_cols, 1) + transfer_well_to_plate(AB_VOL, ab, working_cols, 3) + + h_s.set_and_wait_for_shake_speed(rpm=MIX_SPEEND) + ctx.delay(seconds=MIX_SEC) + + if INCUBATION_ON_DECK == 1: + h_s.set_and_wait_for_shake_speed(rpm=INCUBATION_SPEEND) + ctx.delay(seconds=INCUBATION_MIN * 60) + h_s.deactivate_shaker() + h_s.open_labware_latch() + + else: + # incubation off deck + h_s.deactivate_shaker() + h_s.open_labware_latch() + ctx.pause("Seal the Plate") + ctx.pause("Remove the Seal, Move the Plate to Shaker") + + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + ctx.delay(minutes=MAG_DELAY_MIN) + vol_total = SAMPLE_VOL + AB_VOL + discard(vol_total * 1.1, working_cols) + + ## Wash + for _ in range(WASH_TIMES): + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer_well_to_plate(WASH_VOL, wash, working_cols, 5) + mix(MIX_SPEEND, MIX_SEC) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + discard(WASH_VOL * 1.1, working_cols) + + ## Elution + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer_well_to_plate(ELUTION_VOL, elu, working_cols, 4) + if READY_FOR_SDSPAGE == 1: + ctx.pause("Seal the Working Plate") + h_s.set_and_wait_for_temperature(70) + mix(MIX_SPEEND, MIX_SEC) + ctx.delay(minutes=10) + h_s.deactivate_heater() + h_s.open_labware_latch() + ctx.pause("Protocol Complete") + + elif READY_FOR_SDSPAGE == 0: + mix(MIX_SPEEND, MIX_SEC) + ctx.delay(minutes=2) + temp.set_temperature(4) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + transfer_plate_to_plate(ELUTION_VOL * 1.1, working_cols, final_cols, 6, -5) + ctx.pause("Protocol Complete") + temp.deactivate() diff --git a/app-testing/files/protocols/pl_EM_seq_48Samples_AllSteps_Edits_150.py b/app-testing/files/protocols/pl_EM_seq_48Samples_AllSteps_Edits_150.py new file mode 100644 index 00000000000..3dbfc90a7a1 --- /dev/null +++ b/app-testing/files/protocols/pl_EM_seq_48Samples_AllSteps_Edits_150.py @@ -0,0 +1,1106 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"num_samp":8,"real_mode":true,"end_prep_step":true,"first_wash_step":true,"add_tet_step":true,"second_wash_step":true,"add_formamide_step":true,"third_wash_step":true,"fourth_wash_step":true,"m50_mount":"left","m200_mount":"right","protocol_filename":"EM-seq_48Samples_AllSteps_Edits_150"}""" + ) + return [_all_values[n] for n in names] + + +# flake8: noqa +import math + +from opentrons import protocol_api +from opentrons import types +import random +import math + +metadata = { + "ctx.Name": "NEBNext® Enzymatic Methyl-seq Kit", + "author": "Rami Farawi 1800: + # well_ctr -= 1 + # waste_ctr = 0 + # if well_ctr == 1: + # raise Exception('You have run out of waste storage') + + ethanol_ctr = 10000 + ethanol_well_ctr = 0 + + def aspirate_ethanol(vol, pip): + nonlocal ethanol_well_ctr + nonlocal ethanol_ctr + + # print(ethanol_ctr, ethanol_well_ctr, ethanol[ethanol_well_ctr]) + + pip.aspirate(ethanol_vol, ethanol[ethanol_well_ctr], rate=0.5) + slow_tip_withdrawal(pip, ethanol[ethanol_well_ctr]) + ethanol_ctr -= vol * 8 + if ethanol_ctr < 1600: + ethanol_well_ctr += 1 + ethanol_ctr = 10000 + # print(ethanol_ctr, ethanol_well_ctr, ethanol[ethanol_well_ctr]) + + def remove_super_no_wash(pip, vol, list_of_samples): + + for _ in range(2): + for col in list_of_samples: + pick_up(pip) + pip.aspirate(vol / 2, col.bottom(z=0.7), rate=0.05) + dispose_waste(vol / 2, pip) + pip.drop_tip() if real_mode else pip.return_tip() + ctx.delay(seconds=60) + + def remove_super( + pip, vol, list_of_samples, round, eb_vol, eb_well, list_to_go, eb_mix_vol, plate, deck_location, pcr=False, ethanol=False + ): + + factor = 5 / m200.flow_rate.aspirate + m200.flow_rate.aspirate = m200.flow_rate.aspirate * factor + + pip_rate = 0.2 if pip == m50 else 1 + + if round == 0: # the first round of ethanol wash where we dont go in with 50 + + if pcr: + + for col in list_of_samples: + pick_up(pip) + pip.aspirate(vol / 2, col.bottom(z=5), rate=pip_rate) + ctx.delay(seconds=2) + pip.aspirate(vol / 2, col.bottom(z=0.6), rate=pip_rate) + dispose_waste(vol, pip) + pip.drop_tip() if real_mode else pip.return_tip() + + else: + + for col in list_of_samples: + pick_up(pip) + pip.aspirate(vol - 30, col, rate=pip_rate) + ctx.delay(seconds=2) + pip.aspirate(30, col.bottom(z=0.6), rate=pip_rate) + dispose_waste(vol, pip) + pip.drop_tip() if real_mode else pip.return_tip() + + elif round == 1: # this is the second round of ethanol where we go in with a 50 + + if pcr: + + for i, col in enumerate(list_of_samples): + + if i % 2 == 0: + pick_up(pip) + # pick_up(m50) + pip.aspirate(vol / 2, col.bottom(z=5), rate=pip_rate) + ctx.delay(seconds=2) + pip.aspirate(vol / 2, col.bottom(z=0.6), rate=pip_rate) + dispose_waste(vol, pip) + pip.drop_tip() if real_mode else pip.return_tip() + # m50.aspirate(20, col, rate=0.2) + # m50.aspirate(20, col.bottom(z=0.6), rate=0.2 if pip == m200 else 0.2) + # m50.aspirate(10, col.bottom(z=0.45), rate=0.2 if pip == m200 else 0.2) + # dispose_waste(50, m50) + # m50.drop_tip() if real_mode else m50.return_tip() + + elif i % 2 > 0: + pick_up(pip) + # pick_up(m50) + pip.aspirate(vol / 2, col.bottom(z=5), rate=pip_rate) + ctx.delay(seconds=2) + pip.aspirate(vol / 2, col.bottom(z=0.6), rate=pip_rate) + dispose_waste(vol, pip) + pip.drop_tip() if real_mode else pip.return_tip() + # m50.aspirate(20, col, rate=0.2) + # m50.aspirate(20, col.bottom(z=0.6), rate=0.2 if pip == m200 else 0.2) + # m50.aspirate(10, col.bottom(z=0.45), rate=0.2 if pip == m200 else 0.2) + # dispose_waste(50, m50) + # m50.drop_tip() if real_mode else m50.return_tip() + + ctx.comment("\n ADDING EB \n\n") + + adding_eb(eb_vol, eb_well, list_to_go[i - 1 : i + 1]) + + else: + + for i, col in enumerate(list_of_samples): + + if i % 2 == 0: + + pick_up(pip) + # pick_up(m50) + pip.aspirate(vol - 20, col, rate=pip_rate) + ctx.delay(seconds=2) + pip.aspirate(20, col.bottom(z=0.6), rate=pip_rate) + dispose_waste(vol, pip) + pip.drop_tip() if real_mode else pip.return_tip() + # m50.aspirate(20, col, rate=0.2) + # m50.aspirate(20, col.bottom(z=0.6), rate=0.2 if pip == m200 else 0.2) + # m50.aspirate(10, col.bottom(z=0.45), rate=0.2 if pip == m200 else 0.2) + # dispose_waste(50, m50) + # m50.drop_tip() if real_mode else m50.return_tip() + ctx.comment("\n") + + elif i % 2 > 0: + pick_up(pip) + # pick_up(m50) + pip.aspirate(vol - 20, col, rate=pip_rate) + ctx.delay(seconds=2) + pip.aspirate(20, col.bottom(z=0.6), rate=pip_rate) + dispose_waste(vol, pip) + pip.drop_tip() if real_mode else pip.return_tip() + # m50.aspirate(20, col, rate=0.2) + # m50.aspirate(20, col.bottom(z=0.6), rate=0.2 if pip == m200 else 0.2) + # m50.aspirate(10, col.bottom(z=0.45), rate=0.2 if pip == m200 else 0.2) + # dispose_waste(50, m50) + # m50.drop_tip() if real_mode else m50.return_tip() + + ctx.comment("\n ADDING EB \n\n") + + adding_eb(eb_vol, eb_well, list_to_go[i - 1 : i + 1]) + + ctx.delay(minutes=0.75 if bead_mode else 0.5) + + # -- MOVE DEEPWELL TO DECK + ctx.move_labware(labware=plate, new_location=deck_location, use_gripper=True) + + for d in list_to_go: + pick_up(m50) + m50.aspirate(10, d.top()) + m50.mix(15, eb_mix_vol, d.bottom(z=1), rate=1.2) + m50.dispense(10, d.bottom(z=5)) + ctx.delay(seconds=2) + m50.blow_out() + m50.touch_tip(radius=0.8, v_offset=-5) + m50.drop_tip() if real_mode else m50.return_tip() + + m200.flow_rate.aspirate = m200.flow_rate.aspirate / factor + + def adding_beads(bead_vol, list_of_s, mix_vol): + + if num_col > 3: + pick_up(m200) + for bead_col in beads[:2]: + m200.aspirate(10, bead_col.top()) + m200.mix(10, 190, bead_col, rate=0.4) + m200.mix(2, 190, bead_col, rate=0.20) + slow_tip_withdrawal(m200, bead_col) + ctx.delay(seconds=2) + m200.dispense(10, bead_col.top(-3)) + m200.blow_out(bead_col.bottom(z=20)) + # m200.drop_tip() if real_mode else m200.return_tip() + + isFirst = True #### Reuse mixing tips for first column of samples + + for bead_col, d in zip(beads, list_of_s): + if isFirst: + m200.aspirate(bead_vol, bead_col, rate=0.25) + slow_tip_withdrawal(m200, bead_col) + m200.dispense(bead_vol, d, rate=0.3) + m200.mix(15, mix_vol, d, rate=0.3) + slow_tip_withdrawal(m200, d) + ctx.delay(seconds=3) + m200.blow_out(d.top(z=-2)) + m200.drop_tip() if real_mode else m200.return_tip() + isFirst = False + else: + pick_up(m200) + m200.aspirate(bead_vol, bead_col, rate=0.25) + slow_tip_withdrawal(m200, bead_col) + m200.dispense(bead_vol, d, rate=0.3) + m200.mix(15, mix_vol, d, rate=0.3) + slow_tip_withdrawal(m200, d) + ctx.delay(seconds=3) + m200.blow_out(d.top(z=-2)) + m200.drop_tip() if real_mode else m200.return_tip() + else: + pick_up(m200) + m200.aspirate(10, beads.top()) + m200.mix(10, 190, beads, rate=0.4) + m200.mix(2, 190, beads, rate=0.20) + slow_tip_withdrawal(m200, beads) + ctx.delay(seconds=2) + m200.dispense(10, beads.top(-3)) + m200.blow_out(beads.bottom(z=20)) + # m200.drop_tip() if real_mode else m200.return_tip() + + isFirst = True #### Reuse mixing tips for first column of samples + + for d in list_of_s: + if isFirst: + m200.aspirate(bead_vol, beads, rate=0.25) + slow_tip_withdrawal(m200, beads) + m200.dispense(bead_vol, d, rate=0.3) + m200.mix(15, mix_vol, d, rate=0.3) + slow_tip_withdrawal(m200, d) + ctx.delay(seconds=3) + m200.blow_out(d.top(z=-2)) + m200.drop_tip() if real_mode else m200.return_tip() + isFirst = False + else: + pick_up(m200) + m200.aspirate(bead_vol, beads, rate=0.25) + slow_tip_withdrawal(m200, beads) + m200.dispense(bead_vol, d, rate=0.3) + m200.mix(15, mix_vol, d, rate=0.3) + slow_tip_withdrawal(m200, d) + ctx.delay(seconds=3) + m200.blow_out(d.top(z=-2)) + m200.drop_tip() if real_mode else m200.return_tip() + + def adding_eb(eb_vol, eb_well, list_to_go): + # if first_eb: + # half_of_the_columns = math.floor(num_col/2) + # for i, d in enumerate(list_to_go): + # pick_up(m50) + # m50.aspirate(10, eb_well.top()) + # m50.aspirate(eb_vol, eb_well.bottom(z=4 if i < half_of_the_columns else 1)) + # m50.dispense(eb_vol, d) + # m50.mix(15, eb_mix_vol, d.bottom(z=1), rate=1.2) + # m50.dispense(10, d.bottom(z=5)) + # ctx.delay(seconds=2) + # m50.blow_out() + # m50.touch_tip(radius=0.8, v_offset=-5) + # m50.drop_tip() if real_mode else m50.return_tip() + + pick_up(m50) + for d in list_to_go: + m50.aspirate(10, eb_well.top()) + m50.aspirate(eb_vol, eb_well) + m50.dispense(eb_vol, d.top()) + # m50.mix(15, eb_mix_vol, d.bottom(z=1), rate=1.2) + m50.dispense(10, d.top()) + ctx.delay(seconds=2) + m50.blow_out() + # m50.touch_tip(radius=0.8, v_offset=-5) + m50.drop_tip() if real_mode else m50.return_tip() + + def add_reagent(vol, reagent, mix_vol, sample_list, overflow=False, mix=True): + + if not overflow: + for d in sample_list: + pick_up(m50) + m50.aspirate(vol, reagent) + m50.dispense(vol, d) + if mix: + m50.mix(10 if real_mode else 1, mix_vol, d) + m50.move_to(d.top()) + ctx.delay(seconds=1) + m50.blow_out(d.top(-3)) + m50.drop_tip() if real_mode else m50.return_tip() + + else: + half_of_the_columns = math.floor(num_col / 2) + for i, d in enumerate(sample_list): + pick_up(m50) + m50.aspirate(vol, reagent.bottom(z=5 if i < half_of_the_columns else 1)) + m50.dispense(vol, d) + if mix: + m50.mix(15 if real_mode else 1, mix_vol, d) + m50.move_to(d.top()) + ctx.delay(seconds=1) + m50.blow_out(d.top(-3)) + m50.drop_tip() if real_mode else m50.return_tip() + + def pause(msg): + ctx.comment("\n") + ctx.pause(msg) + ctx.comment("\n") + + ########################################################################## + ########################################################################## + ########################################################################## + ########################################################################## + ############################### REAGENTS ################################## + ########################################################################## + ########################################################################## + ########################################################################## + ########################################################################## + + samples = sample_plate.rows()[0][:num_col] + + # reagent plate on temperature module + end_prep_mmx = temp_reag_plate["A1"] + adapters = temp_reag_plate["A2"] + eb = temp_reag_plate["A3"] # elution buffer for first wash is a3, second wash a4 + eb2 = temp_reag_plate["A4"] + eb3 = temp_reag_plate["A5"] + eb4 = temp_reag_plate["A6"] + tet2 = temp_reag_plate["A7"] + fe2 = temp_reag_plate["A8"] + formamide = temp_reag_plate["A9"] + + # deepwell + + beads = ( + deep_reag_plate["A1"] if num_col <= 3 else [deep_reag_plate["A1"], deep_reag_plate["A2"]] * 100 + ) # BEADS DEPENDENT ON HOW MANY SAMPLES + apobec = deep_reag_plate["A3"] + + # reservoir + + num_tips = 8 + num_washes = 8 # 4 washes each with 2 ethanol washes + total_ethanol = 150 * num_col * num_tips * num_washes + num_ethanol_wells = math.ceil(total_ethanol / 10000) + ctx.pause( + f"""You will need {num_ethanol_wells} ethanol wells in the reservoir all with 10mL each, starting at A1. + Load {10*num_col*1.15}ul of end prep mastermix in column 1 of D1 on temperature module.""" + ) + + if num_col > 3: + ctx.pause(f"""You will need 2 bead columns in the deepwell plate with {345*num_col*1.15/2} in each column, starting at A1.""") + + else: + ctx.pause(f"""You will need 1 bead column in the deepwell plate with {345*num_col*1.15}, in A1.""") + + ethanol = reag_res.wells() + + bead_air = 15 + bead_gap = 15 + + if end_prep_step: + + ########################################################################## + ########################## CAPTURE mRNA ######################### + ########################################################################## + + ctx.comment("\n\n----------ADDING END PREP MMX----------\n") + + add_reagent(10, end_prep_mmx, 35, samples) + + pause( + f"""End Prep MMX added. Place plate in thermal cycler, then back on the temperature module on C1. + Load {31*num_col*1.15 if 31*num_col*1.15 <= 200 else 200}ul of ligation mastermix in column 2 of D1 on temperature module.""" + ) + + # ########################################################################## + # ########################################################################## + + ctx.comment("\n\n----------ADDING ADAPTERS----------\n") + + add_reagent(31, adapters, 45, samples, overflow=True) + + pause("Adapters added. Place in thermal cycler according to the end of section 1.3. Place plate back on temperature module on C1.") + + # ########################################################################## + # ########################################################################## + + ctx.comment("\n\n----------Transferring Sample to Deepwell Plate----------\n") + + factor = 5 / m200.flow_rate.aspirate + m200.flow_rate.aspirate = m200.flow_rate.aspirate * factor + + for s, d in zip(samples, liminal_deep_plate.rows()[0]): + pick_up(m200) + m200.aspirate(94, s.bottom(0.7)) + m200.dispense(94, d) + ctx.delay(seconds=3) + m200.blow_out(d.bottom(z=5)) + m200.drop_tip() if real_mode else m200.return_tip() + + m200.flow_rate.aspirate = m200.flow_rate.aspirate / factor + + if first_wash_step: + # + # # # ########################### FIRST BEAD WASH ############################# + # # # ########################### FIRST BEAD WASH ############################# + # # # ########################### FIRST BEAD WASH ############################# + # # # + liminal_samples = liminal_deep_plate.rows()[0][:num_col] + + ctx.comment("\n\n----------Adding Beads to Sample----------\n") + adding_beads(110, liminal_samples, 130) + + # -- MOVE DEEPWELL TO MAG BLOCK + ctx.move_labware(labware=liminal_deep_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if bead_mode else 0.5) + + ctx.comment("\n\n----------REMOVING SUPER TO TRASH----------\n") + + super_vol = 203.5 + for col in liminal_samples: + pick_up(m200) + m200.aspirate(110, col, rate=0.05) + m200.dispense(110, chute, rate=0.4) + m200.aspirate(110, col.bottom(z=0.6), rate=0.05) + m200.dispense(110, chute, rate=0.4) + m200.drop_tip() if real_mode else m200.return_tip() + + for i in range(2): + ctx.comment("\n\n----------ADDING ETHANOL----------\n") + + ethanol_vol = 150 + pick_up(m200) + for col in liminal_samples: + aspirate_ethanol(ethanol_vol, m200) + m200.dispense(ethanol_vol, col.top(), rate=0.4) + ctx.delay(seconds=3) + m200.blow_out() + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.comment("\n\n----------REMOVING ETHANOL AND ADDING EB----------\n") + remove_super(m200, ethanol_vol, liminal_samples, i, 29, eb, liminal_samples, 20, liminal_deep_plate, 5, ethanol=True) + + # ctx.delay(minutes=2 if bead_mode else 0.5) + # + # # -- MOVE DEEPWELL TO DECK + # ctx.move_labware( + # labware=liminal_deep_plate, + # new_location=5, + # use_gripper=True + # ) + # + # ctx.comment('\n\n----------ADDING EB----------\n') + # adding_eb(29, eb, liminal_samples, 20) + + ctx.delay(minutes=1 if real_mode else 0.05) + + # -- MOVE DEEPWELL TO MAG BLOCK + ctx.move_labware(labware=liminal_deep_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if bead_mode else 0.5) + + samples = sample_plate.rows()[0][6 : 6 + num_col] # REISSUE SAMPLES TO RIGHT HALF OF PLATE + + ctx.comment("\n\n----------Transferring Sample to PCR Plate----------\n") + + for s, d in zip(liminal_samples, samples): + pick_up(m50) + m50.aspirate(28, s.bottom(z=0.6), rate=0.2) + ctx.delay(seconds=2) + m50.aspirate(bead_gap, s.bottom(z=0.6)) + m50.dispense(bead_gap, d.bottom(z=6)) + m50.dispense(28, d) + m50.move_to(d.bottom(z=8)) + ctx.delay(seconds=3) + m50.blow_out() + m50.touch_tip(v_offset=-5, radius=0.75) + m50.drop_tip() if real_mode else m50.return_tip() + + ctx.move_labware(labware=liminal_deep_plate, new_location=5, use_gripper=True) + + pause( + """Beads cleanup completed. Continue with 1.5 Oxidation of 5-Methylcytosines and 5-Hydroxymethylcytosines. Samples are on the right half of the plate, starting in column 7.""" + ) + + ########################### END FIRST BEAD WASH ############################# + ########################### END FIRST BEAD WASH ############################# + ########################### END FIRST BEAD WASH ############################# + + pause( + f"""First wash completed. + Load {17*num_col*1.15}ul of tet2 mastermix in column 7 of D1 on temperature module. + Load {5*num_col*1.15}ul of fe2 mastermix in column 8 of D1 on temperature module. + """ + ) + + if add_tet_step: + ctx.comment("\n\n----------ADDING TET2 MMX----------\n") + add_reagent(17, tet2, 35, samples) + + ctx.comment("\n\n----------ADDING FE2 MMX----------\n") + add_reagent(5, fe2, 40, samples) + + pause("Take the plate to the thermal cycler according to section 1.5 of the SOP. Then, place back on the temperature module.") + + if second_wash_step: + ########################### SECOND BEAD WASH ############################# + ########################### SECOND BEAD WASH ############################# + ########################### SECOND BEAD WASH ############################# + + # samples = sample_plate.rows()[0][6:6+num_col] #un-comment to start at wash 2 + + ctx.comment("\n\n----------Adding Beads to Sample----------\n") + adding_beads(90, samples, 100) + + ctx.delay(minutes=5 if real_mode else 0.05) + + # -- MOVE PCR TO MAG BLOCK + ctx.move_labware(labware=sample_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if bead_mode else 0.5) + + ctx.comment("\n\n----------REMOVING SUPER TO TRASH----------\n") + + super_vol = 150 + + # Removing super in parts. Actual super volume is 141 + for col in samples: + pick_up(m200) + m200.aspirate(57, col, rate=0.05) + m200.dispense(57, chute, rate=0.4) + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.delay(seconds=60) + + for col in samples: + pick_up(m200) + m200.aspirate(96, col, rate=0.05) + m200.dispense(96, chute, rate=0.4) + m200.drop_tip() if real_mode else m200.return_tip() + + for i in range(2): + ctx.comment("\n\n----------ADDING ETHANOL----------\n") + + ethanol_vol = 150 + pick_up(m200) + for col in samples: + aspirate_ethanol(ethanol_vol, m200) + m200.dispense(ethanol_vol, col.top(), rate=0.4) + ctx.delay(seconds=3) + m200.blow_out() + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.comment("\n\n----------REMOVING ETHANOL AND ADDING EB 2----------\n") + remove_super(m200, ethanol_vol, samples, i, 17.5, eb2, samples, 11, sample_plate, sample_temp_adapter, pcr=True, ethanol=True) + + # ctx.delay(minutes=2 if bead_mode else 0.5) + # + # # -- MOVE PCR PLATE TO TEMP MODULE + # ctx.move_labware( + # labware=sample_plate, + # new_location=sample_temp_adapter, # back to temp mod + # use_gripper=True + # ) + + # ctx.comment('\n\n----------ADDING EB----------\n') + # adding_eb(17.5, eb2, samples, 11) + + ctx.delay(minutes=1 if real_mode else 0.05) + + # -- MOVE PCR PLATE TO MAG BLOCK + ctx.move_labware(labware=sample_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if bead_mode else 0.5) + + fresh_samples = fresh_plate.rows()[0][:num_col] + + ctx.comment("\n\n----------Transferring Sample to PCR Plate----------\n") + for s, d in zip(samples, fresh_samples): + pick_up(m50) + m50.aspirate(16, s.bottom(z=0.6), rate=0.2) + ctx.delay(seconds=2) + m50.aspirate(bead_gap, s.bottom(z=0.6)) + m50.dispense(bead_gap, d.bottom(z=6)) + m50.dispense(16, d) + m50.move_to(d.bottom(z=8)) + ctx.delay(seconds=3) + m50.blow_out() + m50.touch_tip(v_offset=-5, radius=0.75) + m50.drop_tip() if real_mode else m50.return_tip() + + samples = fresh_samples + + # -- MOVE SAMPLE PLATE OFF DECK + ctx.move_labware(labware=sample_plate, new_location=chute, use_gripper=True) # OR MOVE IT WITH CHUTE? + + # -- MOVE NEW SAMPLE PLATE TO TEMPERATURE MODULE + ctx.move_labware(labware=fresh_plate, new_location=sample_temp_adapter, use_gripper=True) + + pause( + """Completed step 1.6. Samples are in left half of plate on temperature module. + Safe stopping point. Samples at -20C overnight. Otherwise, select Resume for the robot to proceed to 1.7, adding formamide.""" + ) + + ########################### END SECOND BEAD WASH ############################# + ########################### END SECOND BEAD WASH ############################# + ########################### END SECOND BEAD WASH ############################# + + if add_formamide_step: + ctx.comment("\n\n----------ADDING FORMAMIDE MMX----------\n") + + pause(f"""Load {5*num_col*1.15}ul of formamide mastermix in column 9 of D1 on temperature module.""") + + add_reagent(5, formamide, 10, samples, mix=False) + + pause( + f""" + Formamide is added to samples WITHOUT pipette mixing. Move to thermal cycler. + Load {80*num_col*1.15}ul of apobec mastermix in column 3 of deepwell reagent plate. + """ + ) + + ctx.comment("\n\n----------ADDING APOBEC MMX----------\n") + + for d in samples: + pick_up(m200) + m200.aspirate(80, apobec, rate=0.2) + slow_tip_withdrawal(m200, apobec) + m200.dispense(80, d) + m200.mix(10 if real_mode else 1, 80, d, rate=0.2) + slow_tip_withdrawal(m200, d) + ctx.delay(seconds=3) + m200.blow_out(d.top(-3)) + m200.drop_tip() if real_mode else m200.return_tip() + + pause("APOBEC added. Place in thermal cycler according to the end of section 1.8. Place plate back on temperature module on C1.") + + if third_wash_step: + ########################### THIRD BEAD WASH ############################# + ########################### THIRD BEAD WASH ############################# + ########################### THIRD BEAD WASH ############################# + + liminal_samples = liminal_deep_plate.rows()[0][6 : 6 + num_col] + + ctx.comment("\n\n----------Transferring Sample to Deepwell Plate----------\n") + for s, d in zip(samples, liminal_samples): + pick_up(m200) + m200.aspirate(100, s.bottom(z=0.6), rate=0.05) + ctx.delay(seconds=2) + m200.aspirate(bead_gap, s.bottom(z=0.6)) + m200.dispense(bead_gap, d.bottom(z=6)) + m200.dispense(100, d) + m200.move_to(d.bottom(z=8)) + ctx.delay(seconds=3) + m200.blow_out() + m200.touch_tip(v_offset=-15, radius=0.45) + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.comment("\n\n----------Adding Beads to Sample----------\n") + adding_beads(100, liminal_samples, 130) + + ctx.delay(minutes=5 if real_mode else 0.05) + + # -- MOVE DEEPWELL TO MAG BLOCK + ctx.move_labware(labware=liminal_deep_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if bead_mode else 0.5) + + ctx.comment("\n\n----------REMOVING SUPER TO TRASH----------\n") + + super_vol = 200 + for col in liminal_samples: + pick_up(m200) + m200.aspirate(super_vol - 20, col, rate=0.05) + dispose_waste(super_vol - 20, m200) + m200.aspirate(20, col.bottom(z=0.6), rate=0.05) + dispose_waste(20, m200) + m200.drop_tip() if real_mode else m200.return_tip() + + for i in range(2): + ctx.comment("\n\n----------ADDING ETHANOL----------\n") + + ethanol_vol = 150 + pick_up(m200) + for col in liminal_samples: + aspirate_ethanol(ethanol_vol, m200) + m200.dispense(ethanol_vol, col.top(), rate=0.4) + ctx.delay(seconds=3) + m200.blow_out() + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.comment("\n\n----------REMOVING ETHANOL AND REMOVING EB 3----------\n") + remove_super(m200, ethanol_vol, liminal_samples, i, 21, eb3, liminal_samples, 14, liminal_deep_plate, 5, ethanol=True) + + # ctx.delay(minutes=2 if bead_mode else 0.5) + # + # # -- MOVE DEEPWELL TO DECK + # ctx.move_labware( + # labware=liminal_deep_plate, + # new_location=5, + # use_gripper=True + # ) + # + # ctx.comment('\n\n----------ADDING EB----------\n') + # adding_eb(21, eb3, samples, 14) + + ctx.delay(minutes=1 if real_mode else 0.05) + + # -- MOVE DEEPWELL TO MAG BLOCK + ctx.move_labware(labware=liminal_deep_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if bead_mode else 0.5) + + samples = fresh_plate.rows()[0][6 : 6 + num_col] # REISSUE SAMPLES TO RIGHT HALF OF PLATE + + ctx.comment("\n\n----------Transferring Sample to PCR Plate----------\n") + for s, d in zip(liminal_samples, samples): + pick_up(m50) + m50.aspirate(28, s.bottom(z=0.6), rate=0.2) + ctx.delay(seconds=2) + m50.aspirate(bead_gap, s.bottom(z=0.6), rate=0.2) + m50.dispense(bead_gap, d.bottom(z=6)) + m50.dispense(28, d) + m50.move_to(d.bottom(z=8)) + ctx.delay(seconds=3) + m50.blow_out() + m50.touch_tip(v_offset=-5, radius=0.75) + m50.drop_tip() if real_mode else m50.return_tip() + + ctx.move_labware(labware=liminal_deep_plate, new_location=5, use_gripper=True) + + # -- MOVE PCR PLATE TO DECK + ctx.move_labware(labware=final_plate, new_location=2, use_gripper=False) + + pause( + """Samples are in right half of PCR plate. They can go overnight at -20C. Proceed on to 1.10 of SOP manually. Return plate to complete 1.11 of SOP.""" + ) + + ########################### END THIRD BEAD WASH ############################# + ########################### END THIRD BEAD WASH ############################# + ########################### END THIRD BEAD WASH ############################# + + if fourth_wash_step: + ########################### START FOURTH WASH ############################# + ########################### START FOURTH WASH ############################# + ########################### START FOURTH WASH ############################# + + ctx.comment("\n\n----------Adding Beads to Sample----------\n") + adding_beads(45, samples, 60) + + ctx.delay(minutes=5 if real_mode else 0.05) + + # -- MOVE PCR PLATE TO MAG BLOCK + ctx.move_labware(labware=fresh_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if bead_mode else 0.5) + + ctx.comment("\n\n----------REMOVING SUPER TO TRASH----------\n") + + remove_super_no_wash(m200, 150, samples) + + for i in range(2): + ctx.comment("\n\n----------ADDING ETHANOL----------\n") + + ethanol_vol = 150 + pick_up(m200) + for col in samples: + aspirate_ethanol(ethanol_vol, m200) + m200.dispense(ethanol_vol, col.top(), rate=0.4) + ctx.delay(seconds=3) + m200.blow_out() + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.comment("\n\n----------REMOVING ETHANOL AND ADDING EB 4----------\n") + remove_super(m200, ethanol_vol, samples, i, 21, eb4, samples, 14, fresh_plate, sample_temp_adapter, pcr=True, ethanol=True) + + # ctx.delay(minutes=2 if bead_mode else 0.5) + # + # # MOVING PCR PLATE TO TEMP MOD + # ctx.move_labware( + # labware=fresh_plate, + # new_location=sample_temp_adapter, # back to temp mod + # use_gripper=True + # ) + # + # ctx.comment('\n\n----------ADDING EB----------\n') + # adding_eb(21, eb4, samples, 14) + + ctx.delay(minutes=1 if real_mode else 0.05) + + # -- MOVE FRESH PLATE TO MAG BLOCK + ctx.move_labware(labware=fresh_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if bead_mode else 0.5) + + final_samples = final_plate.rows()[0][:num_col] + + ctx.comment("\n\n----------Transferring Sample to PCR Plate----------\n") + for s, d in zip(samples, final_samples): + pick_up(m50) + m50.aspirate(20, s.bottom(z=0.6), rate=0.2) + ctx.delay(seconds=2) + m50.aspirate(bead_gap, s.bottom(z=0.6)) + m50.dispense(bead_gap, d.bottom(z=6)) + m50.dispense(20, d) + m50.move_to(d.bottom(z=8)) + ctx.delay(seconds=3) + m50.blow_out() + m50.touch_tip(v_offset=-5, radius=0.75) + m50.drop_tip() if real_mode else m50.return_tip() + + samples = final_samples + + # -- MOVE OLD SAMPLE PLATE OFF DECK + ctx.move_labware(labware=fresh_plate, new_location=chute, use_gripper=True) # OR MOVE IT WITH CHUTE? + + # -- MOVE NEW SAMPLE PLATE TO TEMPERATURE MODULE + ctx.move_labware(labware=final_plate, new_location=sample_temp_adapter, use_gripper=True) + + pause("Protocol completed! Samples are on left half of the plate on the temperature module, on slot D1.") + + ########################### END FOURTH BEAD WASH ############################# + ########################### END FOURTH BEAD WASH ############################# + ########################### END FOURTH BEAD WASH ############################# + + # Assigning Liquid and colors + end_prep_mmx_liq = ctx.define_liquid( + name="Beads", + description="Beads", + display_color="#7EFF42", + ) + adapters_liq = ctx.define_liquid( + name="Adapters", + description="Adapters", + display_color="#50D5FF", + ) + eb_liq = ctx.define_liquid( + name="Elution Buffer", + description="Elution Buffer", + display_color="#B925FF", + ) + tet2_liq = ctx.define_liquid( + name="TET2", + description="TET2", + display_color="#FF9900", + ) + fe2_liq = ctx.define_liquid( + name="FE2", + description="FE2", + display_color="#0019FF", + ) + formamide_liq = ctx.define_liquid( + name="FORMAMIDE", + description="FORMAMIDE", + display_color="#007AFF", + ) + beads_liq = ctx.define_liquid( + name="BEADS", + description="BEADS", + display_color="#FF0076", + ) + apobec_liq = ctx.define_liquid( + name="APOBEC", + description="APOBEC", + display_color="#00FFBC", + ) + ethanol_liq = ctx.define_liquid( + name="ETHANOL", + description="ETHANOL", + display_color="#00AAFF", + ) + samples_liq = ctx.define_liquid( + name="SAMPLES", + description="SAMPLES", + display_color="#008000", + ) + + for column in sample_plate.columns()[:num_col]: + for well in column: + well.load_liquid(liquid=samples_liq, volume=50) + + for well in temp_reag_plate.columns()[0]: + well.load_liquid(liquid=end_prep_mmx_liq, volume=10 * 1.15 * num_col) + for well in temp_reag_plate.columns()[1]: + well.load_liquid(liquid=adapters_liq, volume=31 * 1.15 * num_col) + for column, column_volume in zip(temp_reag_plate.columns()[2:6], [29, 17.5, 21, 21]): + for well in column: + well.load_liquid(liquid=eb_liq, volume=column_volume) + for well in temp_reag_plate.columns()[6]: + well.load_liquid(liquid=tet2_liq, volume=17 * 1.15 * num_col) + for well in temp_reag_plate.columns()[7]: + well.load_liquid(liquid=fe2_liq, volume=5 * 1.15 * num_col) + for well in temp_reag_plate.columns()[8]: + well.load_liquid(liquid=formamide_liq, volume=5 * 1.15 * num_col) + + for well in reag_res.wells()[1 : 1 + num_ethanol_wells]: + well.load_liquid(liquid=ethanol_liq, volume=10000) + + if num_col <= 3: + for well in deep_reag_plate.columns()[0]: + well.load_liquid(liquid=beads_liq, volume=345 * 1.15 * num_col) + else: + for column in deep_reag_plate.columns()[:2]: + for well in deep_reag_plate.columns()[0]: + well.load_liquid(liquid=beads_liq, volume=345 * 1.15 * num_col / 2) + + for well in deep_reag_plate.columns()[2]: + well.load_liquid(liquid=apobec_liq, volume=80 * 1.15 * num_col) diff --git a/app-testing/files/protocols/pl_ExpressPlex_96_final.py b/app-testing/files/protocols/pl_ExpressPlex_96_final.py new file mode 100644 index 00000000000..1047ade291d --- /dev/null +++ b/app-testing/files/protocols/pl_ExpressPlex_96_final.py @@ -0,0 +1,144 @@ +def get_values(*names): + import json + + _all_values = json.loads("""{"DryRun":true,"Plates":"1","protocol_filename":"ExpressPlex_96_final"}""") + return [_all_values[n] for n in names] + + +from opentrons import protocol_api + +metadata = {"author": "DAndra Howell ", "source": "Protocol Library"} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.15", +} + + +def run(ctx): + + Plates = 2 + DryRun = True + + try: + [DryRun, Plates] = get_values("DryRun", "Plates") # noqa: F821 + + except NameError: + pass + # get values not defined + + Plates = int(Plates) + + # Modules + heater_shaker = ctx.load_module("heaterShakerModuleV1", "10") + hs_adapter = heater_shaker.load_adapter("opentrons_96_pcr_adapter") + + # Labware + Index_Plate_1 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 1, "Indices_1") + Reagent_Plate_1 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 2, "Reaction Plate_1") + DNA_Plate_1 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 3) + # Locations + Index_1 = Index_Plate_1["A1"] + Reagent_1 = Reagent_Plate_1["A1"] + DNA_1 = DNA_Plate_1["A1"] + tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "4", adapter="opentrons_flex_96_tiprack_adapter") + tiprack_50_2 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "5", adapter="opentrons_flex_96_tiprack_adapter") + if Plates == 2: + Index_Plate_2 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 7, "Indices_2") + Reagent_Plate_2 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 8, "Reaction Plate_2") + DNA_Plate_2 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 9) + # Locations + Index_2 = Index_Plate_2["A1"] + Reagent_2 = Reagent_Plate_2["A1"] + DNA_2 = DNA_Plate_2["A1"] + tiprack_50_3 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "6", adapter="opentrons_flex_96_tiprack_adapter") + tiprack_50_4 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "11", adapter="opentrons_flex_96_tiprack_adapter") + Tips = [tiprack_50_1, tiprack_50_2] if Plates == 1 else [tiprack_50_1, tiprack_50_2, tiprack_50_3, tiprack_50_4] + + # Pipettes + pip = ctx.load_instrument("flex_96channel_1000", "left", tip_racks=Tips) + pip.flow_rate.aspirate = 2 + pip.flow_rate.dispense = 3 + pip.default_speed = 280 + + # Liquid Definitions + Index = ctx.define_liquid(name="Indices 1", description="Index Plate color", display_color="#0000ff") + Index2 = ctx.define_liquid(name="Indices 2", description="Index2 Plate color", display_color="#ff00ff") + for well in Index_Plate_1.wells(): + well.load_liquid(liquid=Index, volume=8) + if Plates == 2: + for well in Index_Plate_2.wells(): + well.load_liquid(liquid=Index2, volume=8) + Reaction_Plate = ctx.define_liquid(name="Reaction Plate", description="Reaction Plate color", display_color="#ff0066") + for well in Reagent_Plate_1.wells(): + well.load_liquid(liquid=Reaction_Plate, volume=8) + if Plates == 2: + for well in Reagent_Plate_2.wells(): + well.load_liquid(liquid=Reaction_Plate, volume=8) + DNA = ctx.define_liquid(name="DNA 1", description="DNA Plate color", display_color="#009900") + DNA2 = ctx.define_liquid(name="DNA 2", description="DNA2 Plate color", display_color="#ffcc00") + for well in DNA_Plate_1.wells(): + well.load_liquid(liquid=DNA, volume=10) + if Plates == 2: + for well in DNA_Plate_2.wells(): + well.load_liquid(liquid=DNA2, volume=10) + + def Index_transfer(vol1, start_loc, end_loc): + pip.pick_up_tip() + pip.aspirate(vol1 + 2, start_loc.bottom(z=1)) # Reverse Pipette as not to introduce bubbles + ctx.delay(seconds=10) + pip.dispense(vol1, end_loc.bottom(z=1.5)) + ctx.delay(seconds=20) + if DryRun == False: + pip.drop_tip() + else: + pip.return_tip() + + def DNA_transfer(vol1, start_loc, end_loc): + pip.pick_up_tip() + pip.aspirate(vol1 + 2, start_loc.bottom(z=1)) # Reverse Pipette as not to introduce bubbles + ctx.delay(seconds=5) + pip.dispense(vol1, end_loc.bottom(z=1.5)) + ctx.delay(seconds=10) + if DryRun == False: + pip.drop_tip() + else: + pip.return_tip() + + def move_plate(labware, new_location): + ctx.move_labware( + labware, + new_location, + use_gripper=False, + ) + + # Commands + heater_shaker.open_labware_latch() + Index_transfer(4, Index_1, Reagent_1) + DNA_transfer(4, DNA_1, Reagent_1) + + ctx.pause("Seal and manually place Reaction Plate 1 onto heater shaker") + move_plate(Reagent_Plate_1, hs_adapter) + heater_shaker.close_labware_latch() + heater_shaker.set_and_wait_for_shake_speed(3000) + ctx.delay(minutes=1.5) + heater_shaker.deactivate_shaker() + heater_shaker.open_labware_latch() + move_plate(Reagent_Plate_1, protocol_api.OFF_DECK) + ctx.comment("Seal and place Reagent Plate 1 into preprogrammed thermocycler") + + if Plates == 2: + Index_transfer(4, Index_2, Reagent_2) + DNA_transfer(4, DNA_2, Reagent_2) + + ctx.pause("Seal and manually place Reagent Plate 2 onto heater shaker") + move_plate(Reagent_Plate_2, hs_adapter) + heater_shaker.close_labware_latch() + heater_shaker.set_and_wait_for_shake_speed(3000) + ctx.delay(minutes=1.5) + heater_shaker.deactivate_shaker() + heater_shaker.open_labware_latch() + move_plate(Reagent_Plate_2, protocol_api.OFF_DECK) + ctx.comment("Seal and place Reagent Plate 2 into preprogrammed thermocycler") + + ctx.home() diff --git a/app-testing/files/protocols/pl_ExpressPlex_Pooling_Final.py b/app-testing/files/protocols/pl_ExpressPlex_Pooling_Final.py new file mode 100644 index 00000000000..6bd15b8f363 --- /dev/null +++ b/app-testing/files/protocols/pl_ExpressPlex_Pooling_Final.py @@ -0,0 +1,276 @@ +def get_values(*names): + import json + + _all_values = json.loads("""{"DryRun":false,"Plates":"4","columns":12,"protocol_filename":"ExpressPlex_Pooling_Final"}""") + return [_all_values[n] for n in names] + + +from opentrons import protocol_api +from opentrons import types +from opentrons.types import Point + +metadata = {"author": "DAndra Howell ", "source": "Protocol Library"} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.15", +} + + +def run(ctx): + # Parameters + DryRun = False + Plates = 4 + if Plates == 1: + columns = 12 + else: + columns = 12 + + try: + [DryRun, Plates, columns] = get_values("DryRun", "Plates", "columns") # noqa: F821 + except NameError: + pass + # get values not defined + + Plates = int(Plates) + + # Modules + heater_shaker = ctx.load_module("heaterShakerModuleV1", "10") + hs_adapter = heater_shaker.load_adapter("opentrons_96_pcr_adapter") + tiprack_200 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "11") + + # Prepare 4 2ml tubes; place in A1, A3, A5 and C1 of tube rack + Final_pool_tube = ctx.load_labware("opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap", "7", "Final Pool") + Pool_Plate = hs_adapter.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Pooling Plate") + + # Deck Setup, Liquids abd Locations + if Plates == 1: + Reagent_Plate_1 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 1, "Ready Reaction Plate 1") + tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "4") + Tips = [tiprack_50_1] + Reagent_1 = Reagent_Plate_1.rows()[0][:12] + Pool_1 = Pool_Plate.wells()[:8] + + Reagent1 = ctx.define_liquid(name="Amplified Libraries_1", description="Amplified Libraries_1", display_color="#ff0000") + for well in Reagent_Plate_1.wells()[: 8 * columns]: + well.load_liquid(liquid=Reagent1, volume=16) + + FP_color1 = ctx.define_liquid(name="Final Pool", description="Final Pool", display_color="#ff0000") + Final_pool_tube.wells()[0].load_liquid(liquid=FP_color1, volume=10 * columns) + + Pool_color1 = ctx.define_liquid(name="Pool 1", description="Pool 1", display_color="#ff0000") + for well in Pool_Plate.wells()[:8]: + well.load_liquid(liquid=Pool_color1, volume=12 * columns) + # Lists + reaction_plates = [Reagent_1] + pool_columns = [Pool_1] + pool_tubes = ["A1"] + + if Plates == 2: + Reagent_Plate_1 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 1, "Ready Reaction Plate 1") + tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "4") + Reagent_Plate_2 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 2, "Ready Reaction Plate 2") + tiprack_50_2 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "5") + Tips = [tiprack_50_1, tiprack_50_2] + Reagent_1 = Reagent_Plate_1.rows()[0][:12] + Pool_1 = Pool_Plate.wells()[:8] + + Reagent_2 = Reagent_Plate_2.rows()[0][:12] + Pool_2 = Pool_Plate.wells()[16:24] + + Reagent1 = ctx.define_liquid(name="Amplified Libraries_1", description="Amplified Libraries_1", display_color="#ff0000") + for well in Reagent_Plate_1.wells(): + well.load_liquid(liquid=Reagent1, volume=16) + Reagent2 = ctx.define_liquid(name="Amplified Libraries_2", description="Amplified Libraries_1", display_color="#ff66cc") + for well in Reagent_Plate_2.wells(): + well.load_liquid(liquid=Reagent2, volume=16) + + FP_color1 = ctx.define_liquid(name="Final Pool_1", description="Final Pool_1", display_color="#ff0000") + Final_pool_tube.wells()[0].load_liquid(liquid=FP_color1, volume=120) + FP_color2 = ctx.define_liquid(name="Final Pool_2", description="Final Pool_2", display_color="#ff66cc") + Final_pool_tube.wells()[8].load_liquid(liquid=FP_color2, volume=120) + + Pool_color1 = ctx.define_liquid(name="Pool 1", description="Pool 1", display_color="#ff0000") + for well in Pool_Plate.wells()[:8]: + well.load_liquid(liquid=Pool_color1, volume=144) + Pool_color2 = ctx.define_liquid(name="Pool 2", description="Pool 2", display_color="#ff66cc") + for well in Pool_Plate.wells()[16:24]: + well.load_liquid(liquid=Pool_color2, volume=144) + + # Lists + reaction_plates = [Reagent_1, Reagent_2] + pool_columns = [Pool_1, Pool_2] + pool_tubes = ["A1", "A3"] + + if Plates == 3: + Reagent_Plate_1 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 1, "Ready Reaction Plate 1") + tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "4") + Reagent_Plate_2 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 2, "Ready Reaction Plate 2") + tiprack_50_2 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "5") + Reagent_Plate_3 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 3, "Ready Reaction Plate 3") + tiprack_50_3 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "6") + Tips = [tiprack_50_1, tiprack_50_2, tiprack_50_3] + Reagent_1 = Reagent_Plate_1.rows()[0][:12] + Pool_1 = Pool_Plate.wells()[:8] + + Reagent_2 = Reagent_Plate_2.rows()[0][:12] + Pool_2 = Pool_Plate.wells()[16:24] + + Reagent_3 = Reagent_Plate_3.rows()[0][:12] + Pool_3 = Pool_Plate.wells()[32:40] + + Reagent1 = ctx.define_liquid(name="Amplified Libraries_1", description="Amplified Libraries_1", display_color="#ff0000") + for well in Reagent_Plate_1.wells(): + well.load_liquid(liquid=Reagent1, volume=16) + Reagent2 = ctx.define_liquid(name="Amplified Libraries_2", description="Amplified Libraries_2", display_color="#ff66cc") + for well in Reagent_Plate_2.wells(): + well.load_liquid(liquid=Reagent2, volume=16) + Reagent3 = ctx.define_liquid(name="Amplified Libraries_3", description="Amplified Libraries_3", display_color="#00ff99") + for well in Reagent_Plate_3.wells(): + well.load_liquid(liquid=Reagent3, volume=16) + + FP_color1 = ctx.define_liquid(name="Final Pool_1", description="Final Pool_1", display_color="#ff0000") + Final_pool_tube.wells()[0].load_liquid(liquid=FP_color1, volume=120) + FP_color2 = ctx.define_liquid(name="Final Pool_2", description="Final Pool_2", display_color="#ff66cc") + Final_pool_tube.wells()[8].load_liquid(liquid=FP_color2, volume=120) + FP_color3 = ctx.define_liquid(name="Final Pool_3", description="Final Pool_3", display_color="#00ff99") + Final_pool_tube.wells()[16].load_liquid(liquid=FP_color3, volume=120) + + Pool_color1 = ctx.define_liquid(name="Pool 1", description="Pool 1", display_color="#ff0000") + for well in Pool_Plate.wells()[:8]: + well.load_liquid(liquid=Pool_color1, volume=144) + Pool_color2 = ctx.define_liquid(name="Pool 2", description="Pool 2", display_color="#ff66cc") + for well in Pool_Plate.wells()[16:24]: + well.load_liquid(liquid=Pool_color2, volume=144) + Pool_color3 = ctx.define_liquid(name="Pool 3", description="Pool 2", display_color="#00ff99") + for well in Pool_Plate.wells()[32:40]: + well.load_liquid(liquid=Pool_color3, volume=144) + + # Lists + reaction_plates = [Reagent_1, Reagent_2, Reagent_3] + pool_columns = [Pool_1, Pool_2, Pool_3] + pool_tubes = ["A1", "A3", "A5"] + + if Plates == 4: + Reagent_Plate_1 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 1, "Ready Reaction Plate 1") + tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "4") + Reagent_Plate_2 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 2, "Ready Reaction Plate 2") + tiprack_50_2 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "5") + Reagent_Plate_3 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 3, "Ready Reaction Plate 3") + tiprack_50_3 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "6") + Reagent_Plate_4 = ctx.load_labware("biorad_96_wellplate_200ul_pcr", 8, "Ready Reaction Plate 4") + tiprack_50_4 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "9") + Tips = [tiprack_50_1, tiprack_50_2, tiprack_50_3, tiprack_50_4] + Reagent_1 = Reagent_Plate_1.rows()[0][:12] + Pool_1 = Pool_Plate.wells()[:8] + + Reagent_2 = Reagent_Plate_2.rows()[0][:12] + Pool_2 = Pool_Plate.wells()[16:24] + + Reagent_3 = Reagent_Plate_3.rows()[0][:12] + Pool_3 = Pool_Plate.wells()[32:40] + + Reagent_4 = Reagent_Plate_4.rows()[0][:12] + Pool_4 = Pool_Plate.wells()[48:56] + + Reagent1 = ctx.define_liquid(name="Amplified Libraries_1", description="Amplified Libraries_1", display_color="#ff0000") + for well in Reagent_Plate_1.wells(): + well.load_liquid(liquid=Reagent1, volume=16) + Reagent2 = ctx.define_liquid(name="Amplified Libraries_2", description="Amplified Libraries_2", display_color="#ff66cc") + for well in Reagent_Plate_2.wells(): + well.load_liquid(liquid=Reagent2, volume=16) + Reagent3 = ctx.define_liquid(name="Amplified Libraries_3", description="Amplified Libraries_3", display_color="#00ff99") + for well in Reagent_Plate_3.wells(): + well.load_liquid(liquid=Reagent3, volume=16) + Reagent4 = ctx.define_liquid(name="Amplified Libraries_4", description="Amplified Libraries_4", display_color="#0066ff") + for well in Reagent_Plate_4.wells(): + well.load_liquid(liquid=Reagent4, volume=16) + + FP_color1 = ctx.define_liquid(name="Final Pool_1", description="Final Pool_1", display_color="#ff0000") + Final_pool_tube.wells()[0].load_liquid(liquid=FP_color1, volume=120) + FP_color2 = ctx.define_liquid(name="Final Pool_2", description="Final Pool_2", display_color="#ff66cc") + Final_pool_tube.wells()[8].load_liquid(liquid=FP_color2, volume=120) + FP_color3 = ctx.define_liquid(name="Final Pool_3", description="Final Pool_3", display_color="#00ff99") + Final_pool_tube.wells()[16].load_liquid(liquid=FP_color3, volume=120) + FP_color4 = ctx.define_liquid(name="Final Pool_4", description="Final Pool_4", display_color="#0066ff") + Final_pool_tube.wells()[2].load_liquid(liquid=FP_color4, volume=120) + + Pool_color1 = ctx.define_liquid(name="Pool 1", description="Pool 1", display_color="#ff0000") + for well in Pool_Plate.wells()[:8]: + well.load_liquid(liquid=Pool_color1, volume=144) + Pool_color2 = ctx.define_liquid(name="Pool 2", description="Pool 2", display_color="#ff66cc") + for well in Pool_Plate.wells()[16:24]: + well.load_liquid(liquid=Pool_color2, volume=144) + Pool_color3 = ctx.define_liquid(name="Pool 3", description="Pool 2", display_color="#00ff99") + for well in Pool_Plate.wells()[32:40]: + well.load_liquid(liquid=Pool_color3, volume=144) + Pool_color4 = ctx.define_liquid(name="Pool 4", description="Pool 4", display_color="#0066ff") + for well in Pool_Plate.wells()[48:56]: + well.load_liquid(liquid=Pool_color4, volume=144) + + # Lists + reaction_plates = [Reagent_1, Reagent_2, Reagent_3, Reagent_4] + pool_columns = [Pool_1, Pool_2, Pool_3, Pool_4] + pool_tubes = ["A1", "A3", "A5", "C1"] + + # Pipettes + p20_multi = ctx.load_instrument("flex_8channel_50", "right", tip_racks=Tips) + p20_multi.flow_rate.aspirate = 4 + p20_multi.flow_rate.dispense = 3 + p20_multi.flow_rate.blow_out = 1 + p1000_single = ctx.load_instrument("flex_1channel_1000", "left", tip_racks=[tiprack_200]) + p1000_single.flow_rate.blow_out = 10 + p1000_single.flow_rate.dispense = 50 + p1000_single.flow_rate.aspirate = 100 + + def Strip_tube_pooling(start, end): + for x in range(columns): + p20_multi.pick_up_tip() + p20_multi.aspirate(12, start[x].bottom(z=0.5), rate=0.5) + ctx.delay(seconds=10) + p20_multi.dispense(12, end[0].top().move(types.Point(z=-7, x=-2.7))) + ctx.delay(seconds=10) + p20_multi.blow_out() + if DryRun == True: + p20_multi.return_tip() + else: + p20_multi.drop_tip() + + def Final_tube_pooling(start, end): + for z in range(8): + p1000_single.pick_up_tip() + p1000_single.aspirate(10 * columns, start[z].bottom(z=1)) + ctx.delay(seconds=10) + p1000_single.dispense(10 * columns, Final_pool_tube[end].top().move(types.Point(z=-5, x=-4.3))) + ctx.delay(seconds=10) + p1000_single.blow_out() + if DryRun == True: + p1000_single.return_tip() + else: + p1000_single.drop_tip() + + # Commands + heater_shaker.close_labware_latch() + ctx.pause("Pulse spin plates once amplification is finished") + + # Begin pooling + for a, b in zip(reaction_plates, pool_columns): + Strip_tube_pooling(a, b) + + # Mix pool on heater shaker + ctx.pause("Thouroughly seal plate and return to Heater Shaker") + heater_shaker.set_and_wait_for_shake_speed(3000) + ctx.delay(minutes=1.5) + heater_shaker.deactivate_shaker() + heater_shaker.open_labware_latch() + ctx.pause("Pulse spin plate, carefully unseal and return to Heater Shaker") + heater_shaker.close_labware_latch() + + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Start final tube pooling + ctx.comment("Begin pooling in 2ml tubes") + for c, d in zip(pool_columns, pool_tubes): + Final_tube_pooling(c, d) + + heater_shaker.open_labware_latch() + ctx.home() diff --git a/app-testing/files/protocols/pl_Flex_Protein_Digestion_Protocol.py b/app-testing/files/protocols/pl_Flex_Protein_Digestion_Protocol.py new file mode 100644 index 00000000000..3fe460b7050 --- /dev/null +++ b/app-testing/files/protocols/pl_Flex_Protein_Digestion_Protocol.py @@ -0,0 +1,64 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"num_plates":4,"aspirate_height":5,"dispense_height":5,"buffer_vol":100,"num_mix":3,"mix_vol":100,"mix_height":5,"protocol_filename":"Flex_Protein_Digestion_Protocol"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons import protocol_api +import json + + +# metadata +metadata = { + "protocolName": "Flex Digestion Protocol", + "author": "", + "description": "Liquid transfer using 96 channel pipette", +} + +# requirements +requirements = {"robotType": "Flex", "apiLevel": "2.16"} + + +# protocol run function +def run(ctx: protocol_api.ProtocolContext): + + [num_plates, aspirate_height, dispense_height, buffer_vol, num_mix, mix_vol, mix_height] = get_values( # noqa: F821 + "num_plates", "aspirate_height", "dispense_height", "buffer_vol", "num_mix", "mix_vol", "mix_height" + ) + + # labware + assay_plates = ( + ctx.load_labware("thermo_96_wellplate_2200ul", location=slot, namespace="custom_beta") + for slot in ["C2", "C3", "D2", "D3"][:num_plates] + ) + assay_reservoir = ctx.load_labware("nest_1_reservoir_290ml", location="C1") + tiprack1 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", location="A1", adapter="opentrons_flex_96_tiprack_adapter") + tiprack2 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", location="A2", adapter="opentrons_flex_96_tiprack_adapter") + tiprack3 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", location="B1", adapter="opentrons_flex_96_tiprack_adapter") + tiprack4 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", location="B2", adapter="opentrons_flex_96_tiprack_adapter") + + trash = ctx.load_trash_bin("A3") + + # pipettes + flex96 = ctx.load_instrument(instrument_name="flex_96channel_1000", mount="left", tip_racks=[tiprack1, tiprack2, tiprack3, tiprack4]) + + # Helper Functions + def mix(pip): + for i in range(num_mix): + pip.aspirate(mix_vol, plate["A1"].bottom(dispense_height)) + pip.dispense(mix_vol, plate["A1"].bottom(dispense_height)) + + # Step 1 - Transfer assay buffer from Reservoir to Assay Plate: + for plate in assay_plates: + flex96.pick_up_tip() + flex96.aspirate(buffer_vol, assay_reservoir["A1"].bottom(z=aspirate_height)) + flex96.dispense(buffer_vol, plate["A1"].bottom(z=dispense_height)) + flex96.mix(num_mix, mix_vol) + flex96.blow_out() + flex96.touch_tip() + flex96.drop_tip() + + ctx.comment("End of Protocol") diff --git a/app-testing/files/protocols/pl_Flex_customizable_serial_dilution_upload.py b/app-testing/files/protocols/pl_Flex_customizable_serial_dilution_upload.py new file mode 100644 index 00000000000..8c78652f865 --- /dev/null +++ b/app-testing/files/protocols/pl_Flex_customizable_serial_dilution_upload.py @@ -0,0 +1,122 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"pipette_type":"flex_8channel_1000","mount_side":"right","tip_type":"1000f","trough_type":"nest_12_reservoir_15ml","plate_type":"nest_96_wellplate_200ul_flat","dilution_factor":3,"num_of_dilutions":10,"total_mixing_volume":150,"blank_on":true,"tip_use_strategy":"never","air_gap_volume":10,"protocol_filename":"Flex_customizable_serial_dilution_upload"}""" + ) + return [_all_values[n] for n in names] + + +"""DETAILS.""" + +metadata = {"protocolName": "Customizable Serial Dilution", "author": "Opentrons ", "source": "Protocol Library"} + +requirements = {"robotType": "OT-3", "apiLevel": "2.15"} + + +def run(protocol_context): + """PROTOCOL BODY.""" + [ + pipette_type, + mount_side, + tip_type, + trough_type, + plate_type, + dilution_factor, + num_of_dilutions, + total_mixing_volume, + blank_on, + tip_use_strategy, + air_gap_volume, + ] = get_values( # noqa: F821 + "pipette_type", + "mount_side", + "tip_type", + "trough_type", + "plate_type", + "dilution_factor", + "num_of_dilutions", + "total_mixing_volume", + "blank_on", + "tip_use_strategy", + "air_gap_volume", + ) + # check for bad setup here + if not 1 <= num_of_dilutions <= 11: + raise Exception("Enter a number of dilutions between 1 and 11") + + if num_of_dilutions == 11 and blank_on == 1: + raise Exception("No room for blank with 11 dilutions") + + tip_types_dict = { + "50f": "opentrons_flex_96_filtertiprack_50ul", + "50": "opentrons_flex_96_tiprack_50ul", + "200f": "opentrons_flex_96_filtertiprack_200ul", + "200": "opentrons_flex_96_tiprack_200ul", + "1000f": "opentrons_flex_96_filtertiprack_1000ul", + "1000": "opentrons_flex_96_tiprack_1000ul", + } + + # labware + trough = protocol_context.load_labware(trough_type, "2") + plate = protocol_context.load_labware(plate_type, "3") + tip_name = tip_types_dict[tip_type] + tipracks = [protocol_context.load_labware(tip_name, slot) for slot in ["1", "4"]] + + # pipette + pipette = protocol_context.load_instrument(pipette_type, mount_side, tipracks) + pip_channel = float(pipette_type.split("_")[1][0]) + + # reagents + diluent = trough.wells()[0] + source = plate.columns()[0] + + # define liquids (dilutent + original samples) + dilutent_liquid = protocol_context.define_liquid( + name="Dilutent", description="Diluent liquid is filled in the reservoir", display_color="#33FF33" + ) + sample_liquid = protocol_context.define_liquid( + name="Sample", description="Non-diluted samples are loaded in the 1st column", display_color="#FF0000" + ) + # load dilutent + diluent.load_liquid(liquid=dilutent_liquid, volume=0.8 * diluent.max_volume) + # load sample + for well in source: + well.load_liquid(liquid=sample_liquid, volume=total_mixing_volume) + + transfer_volume = total_mixing_volume / dilution_factor + diluent_volume = total_mixing_volume - transfer_volume + + if pip_channel == 8: + dilution_destination_sets = [[row] for row in plate.rows()[0][1 : num_of_dilutions + 1]] + dilution_source_sets = [[row] for row in plate.rows()[0][:num_of_dilutions]] + blank_set = [plate.rows()[0][num_of_dilutions + 1]] + else: + dilution_destination_sets = plate.columns()[1 : num_of_dilutions + 1] + dilution_source_sets = plate.columns()[:num_of_dilutions] + blank_set = plate.columns()[num_of_dilutions + 1] + all_diluent_destinations = [well for set in dilution_destination_sets for well in set] + + pipette.pick_up_tip() + for dest in all_diluent_destinations: + # Distribute diluent across the plate to the the number of samples + # And add diluent to one column after the number of samples for a blank + pipette.transfer(diluent_volume, diluent, dest, air_gap=air_gap_volume, new_tip="never") + pipette.drop_tip() + + # Dilution of samples across the 96-well flat bottom plate + if tip_use_strategy == "never": + pipette.pick_up_tip() + for source_set, dest_set in zip(dilution_source_sets, dilution_destination_sets): + for s, d in zip(source_set, dest_set): + pipette.transfer( + transfer_volume, s, d, air_gap=air_gap_volume, mix_after=(5, total_mixing_volume / 2), new_tip=tip_use_strategy + ) + if tip_use_strategy == "never": + pipette.drop_tip() + + if blank_on: + pipette.pick_up_tip() + for blank_well in blank_set: + pipette.transfer(diluent_volume, diluent, blank_well, air_gap=air_gap_volume, new_tip="never") + pipette.drop_tip() diff --git a/app-testing/files/protocols/pl_Hyperplus_tiptracking_V4_final.py b/app-testing/files/protocols/pl_Hyperplus_tiptracking_V4_final.py new file mode 100644 index 00000000000..77c8f7bb158 --- /dev/null +++ b/app-testing/files/protocols/pl_Hyperplus_tiptracking_V4_final.py @@ -0,0 +1,1136 @@ +from opentrons import protocol_api +from opentrons import types +import math +import numpy as np + +metadata = {"protocolName": "KAPA HyperPlus Library Preparation", "author": "Your Name "} + +requirements = {"robotType": "Flex", "apiLevel": "2.18"} + +tt_50 = 0 +tt_200 = 0 +p50_rack_count = 0 +p200_rack_count = 0 +tip50 = 50 +tip200 = 200 +p50_racks_ondeck = [] +p200_racks_ondeck = [] +p50_racks_offdeck = [] +p200_racks_offdeck = [] + + +def add_parameters(parameters): + + parameters.add_bool( + variable_name="dry_run", display_name="Dry Run", description="Skip incubation delays and shorten mix steps.", default=False + ) + + parameters.add_bool(variable_name="trash_tips", display_name="Trash tip", description="tip thrases after every use", default=True) + + parameters.add_bool( + variable_name="heater_shaker", + display_name="Heater Shaker", + description="Note: only use if heatershaker is not on deck", + default=False, + ) + + parameters.add_int( + variable_name="num_samples", + display_name="number of samples", + description="How many samples to be perform for library prep", + default=8, + minimum=8, + maximum=48, + ) + + parameters.add_int( + variable_name="PCRCYCLES", + display_name="number of PCR Cycles", + description="How many pcr cycles to be perform for library prep", + default=2, + minimum=2, + maximum=16, + ) + + parameters.add_int( + variable_name="Fragmentation_time", + display_name="time on thermocycler", + description="Fragmentation time in thermocycler", + default=10, + minimum=10, + maximum=30, + ) + + +def run(ctx): + # Flexible Parameters for Customers to Change from Protocol Library Page + + USE_GRIPPER = True + trash_tips = ctx.params.trash_tips + heater_shaker = ctx.params.heater_shaker # Is there a heater-shaker present? + dry_run = ctx.params.dry_run + REUSE_ETOH_TIPS = False + REUSE_RSB_TIPS = False # Reuse tips for RSB buffer (adding RSB, mixing, and transferring) + REUSE_REMOVE_TIPS = False # Reuse tips for supernatant removal + num_samples = ctx.params.num_samples + PCRCYCLES = ctx.params.PCRCYCLES + Fragmentation_time = 10 + ligation_tc_time = 15 + Fragmentation_Mix_time = 60 + End_Repair_Mix_time = 60 + Ligation_Mix_time = 60 + amplification_Mix_time = 60 + ################################################################################ + # Beginning Protocol- Setting Variables # + ################################################################################ + if dry_run: + trash_tips = False + + num_cols = math.ceil(num_samples / 8) + + # Pre-set parameters + sample_vol = 35 + frag_vol = 15 + end_repair_vol = 10 + adapter_vol = 5 + ligation_vol = 45 + amplification_vol = 30 + bead_vol_1 = 88 + bead_vol_2 = 50 + bead_vol = bead_vol_1 + bead_vol_2 + bead_inc = 2 + rsb_vol_1 = 25 + rsb_vol_2 = 20 + rsb_vol = rsb_vol_1 + rsb_vol_2 + elution_vol = 20 + elution_vol_2 = 17 + etoh_vol = 400 + + # Importing Labware, Modules and Instruments + magblock = ctx.load_module("magneticBlockV1", "D2") + temp_mod = ctx.load_module("temperature module gen2", "C1") + temp_adapter = temp_mod.load_adapter("opentrons_96_well_aluminum_block") + temp_plate = temp_adapter.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Temp Module Reservoir Plate") + + if not dry_run: + temp_mod.set_temperature(4) + tc_mod = ctx.load_module("thermocycler module gen2") + # Just in case + tc_mod.open_lid() + + FLP_plate = magblock.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "FLP Plate") + samples_flp = FLP_plate.rows()[0][:num_cols] + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_pcr_adapter") + sample_plate = h_s_adapter.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Sample Plate") + # Just in Case + h_s.close_labware_latch() + + else: + sample_plate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "D1", "Sample Pate") + + sample_plate_2 = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "B2", "Sample Pate") + samples_2 = sample_plate_2.rows()[0][:num_cols] + liq_samples = sample_plate.rows()[:num_cols] + samples = sample_plate.rows()[0][:num_cols] + reservoir = ctx.load_labware("nest_96_wellplate_2ml_deep", "C2") + + trash = ctx.load_waste_chute() + + # Import Global Variables + + global tip50 + global tip200 + global p50_rack_count + global p200_rack_count + global tt_50 + global tt_200 + + p200 = ctx.load_instrument("flex_8channel_1000", "left") + p50 = ctx.load_instrument("flex_8channel_50", "right") + + p50_racks = [] + p200_racks = [] + + Available_on_deck_slots = ["A2", "A3", "B3", "C3"] + Available_off_deck_slots = ["A4", "B4", "C4"] + p50_racks_to_dump = [] + p200_racks_to_dump = [] + + if REUSE_RSB_TIPS: + Available_on_deck_slots.remove("A3") + tip50_reuse = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "A3") + RSB_tip = [] + p50_rack_count += 1 + tt_50 += 12 + p50.tip_racks.append(tip50_reuse) + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count}") + for x in range(num_cols): + RSB_tip.append(tip50_reuse.wells()[8 * x]) + tt_50 -= 1 + p50.starting_tip = tip50_reuse.wells()[(len(RSB_tip)) * 8] + + if REUSE_REMOVE_TIPS: + Available_on_deck_slots.remove("A2") + tip200_reuse = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A2") + RemoveSup_tip = [] + p200_rack_count += 1 + tt_200 += 12 + p200.tip_racks.append(tip200_reuse) + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count}") + for x in range(num_cols): + RemoveSup_tip.append(tip200_reuse.wells()[8 * x]) + tt_200 -= 1 + p200.starting_tip = tip200_reuse.wells()[(len(RemoveSup_tip)) * 8] + + ################################################################################ + # Load Reagent Locations in Reservoirs # + ################################################################################ + """ + colors = ['#008000','#008000','#A52A2A','#00FFFF','#0000FF','#800080',\ + '#ADD8E6','#FF0000','#FFFF00','#FF00FF','#00008B','#7FFFD4',\ + '#FFC0CB','#FFA500','#00FF00','#C0C0C0'] + """ + ##############Sample Plate############## + sample_liq = ctx.define_liquid(name="Samples", description="DNA sample of known quantity", display_color="#C0C0C0") + for well in sample_plate.wells()[: 8 * num_cols]: + well.load_liquid(liquid=sample_liq, volume=sample_vol) + + Final_liq = ctx.define_liquid(name="Final Library", description="Final Library", display_color="#FFA500") + for well in sample_plate_2.wells()[: 8 * num_cols]: + well.load_liquid(liquid=Final_liq, volume=elution_vol_2) + + # Cold Res + + adapters = temp_plate.rows()[0][:num_cols] # used for filling liquids + adapters_ = adapters[0] # used for pipetting into/ out of + adapter_liq = ctx.define_liquid(name="Adapters", description="Adapters to ligate onto DNA insert.", display_color="#A52A2A") + for well in temp_plate.wells()[: 8 * num_cols]: + well.load_liquid(liquid=adapter_liq, volume=adapter_vol * 2) + + end_repair = temp_plate.columns()[num_cols] # used for filling liquids + er_res = end_repair[0] + er_liq = ctx.define_liquid(name="End Repair", description="End Repair mix", display_color="#FF00FF") + for well in end_repair: + well.load_liquid(liquid=er_liq, volume=(end_repair_vol * num_cols) + (0.1 * end_repair_vol)) + + frag = temp_plate.columns()[num_cols + 1] + frag_res = frag[0] + frag_liq = ctx.define_liquid(name="Fragmentation", description="Fragmentation mix", display_color="#00FFFF") + for well in frag: + well.load_liquid(liquid=frag_liq, volume=(frag_vol * num_cols) + (0.1 * frag_vol)) + + ligation = temp_plate.columns()[num_cols + 2] + ligation_res = ligation[0] + ligation_liq = ctx.define_liquid(name="Ligation", description="Ligation Mix", display_color="#008000") + for well in ligation: + well.load_liquid(liquid=ligation_liq, volume=(ligation_vol * num_cols) + (0.1 * ligation_vol)) + + lib_amplification = temp_plate.columns()[num_cols + 3] + amplification_res = lib_amplification[0] + amp_liq = ctx.define_liquid(name="Amplification", description="Amplification Mix", display_color="#0000FF") + for well in lib_amplification: + well.load_liquid(liquid=amp_liq, volume=(amplification_vol * num_cols) + (0.1 * amplification_vol)) + + # Room Temp Res (deepwell) + bead = reservoir.columns()[0] + bead_res = bead[0] + bead_liq = ctx.define_liquid(name="Ampure Beads", description="Ampure Beads", display_color="#800080") + for well in bead: + well.load_liquid(liquid=bead_liq, volume=(bead_vol * num_cols) + (0.1 * bead_vol * num_cols)) + + rsb = reservoir.columns()[3] + rsb_res = rsb[0] + rsb_liq = ctx.define_liquid(name="RSB", description="Resuspension buffer", display_color="#FFFF00") + for well in rsb: + well.load_liquid(liquid=rsb_liq, volume=(rsb_vol * num_cols) + (0.1 * rsb_vol * num_cols)) + + etoh1 = reservoir.columns()[4] + etoh1_res = etoh1[0] + etoh_liq = ctx.define_liquid(name="Ethanol 80%", description="Fresh 80% Ethanol", display_color="#FF00FF") + for well in etoh1: + well.load_liquid(liquid=etoh_liq, volume=(etoh_vol * num_cols) + (0.1 * etoh_vol * num_cols)) + + etoh2 = reservoir.columns()[5] + etoh2_res = etoh2[0] + for well in etoh2: + well.load_liquid(liquid=etoh_liq, volume=(etoh_vol * num_cols) + (0.1 * etoh_vol * num_cols)) + + waste1 = reservoir.columns()[6] + waste1_res = waste1[0] + + waste2 = reservoir.columns()[7] + waste2_res = waste2[0] + + ################################################################################ + # Starting to Create Function Definitions # + ################################################################################ + def tiptrack(rack, reuse_col, reuse=False): + global tt_50 + global tt_200 + global p50_racks_ondeck + global p200_racks_ondeck + global p50_racks_offdeck + global p200_racks_offdeck + global p50_rack_count + global p200_rack_count + + if rack == tip50: + if tt_50 == 0 and not reuse: # If this is the first column of tip box and these aren't reused tips + ctx.comment("Troubleshoot") + if len(Available_on_deck_slots) > 0: + """ + If there are open deck slots --> need to add a new tip box before pickup + """ + p50_rack_count += 1 + tt_50 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_50ul", Available_on_deck_slots[0], f"50 ul Tip Rack #{p50_rack_count}" + ) + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count} to slot {Available_on_deck_slots[0]}") + Available_on_deck_slots.pop(0) + p50_racks_ondeck.append(addtiprack) + p50_racks_to_dump.append(addtiprack) + p50.tip_racks.append(addtiprack) + elif len(Available_on_deck_slots) == 0 and len(Available_off_deck_slots) > 0: + p50_rack_count += 1 + tt_50 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_50ul", Available_off_deck_slots[0], f"50 ul Tip Rack #{p50_rack_count}" + ) + Available_off_deck_slots.pop( + 0 + ) # Load rack into staging area slot to be moved on deck- want this slot removed so we know when we need manual addition + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count}") + p50_racks_offdeck.append(addtiprack) # used in TipSwap then deleted once it is moved + p50.tip_racks.append(addtiprack) # lets pipette know it can use this rack now + TipSwap(50) # Throw first tip box out and replace with a box from staging area + elif ( + len(Available_on_deck_slots) == 0 and len(Available_off_deck_slots) == 0 + ): # If there are no tip racks on deck or in staging area to use + ctx.pause("Please place a new 50ul Tip Rack in slot A4") + p50_rack_count += 1 + tt_50 += 12 + addtiprack = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "A4", f"50 ul Tip Rack #{p50_rack_count}") + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count}") + p50_racks_offdeck.append(addtiprack) # used in TipSwap, then deleted once it is moved + p50.tip_racks.append(addtiprack) # lets pipette know it can use this rack now + TipSwap(50) # Throw first tip box out and replace with a box from staging area + # Call where tips will actually be picked up + if reuse and REUSE_RSB_TIPS: + p50.pick_up_tip(tip50_reuse.wells()[8 * reuse_col]) + else: + tt_50 -= 1 + ctx.comment("Column " + str(12 - tt_50)) + ctx.comment("Available On Deck Slots:" + str(len(Available_on_deck_slots))) + ctx.comment("Available Off Deck Slots:" + str(len(Available_off_deck_slots))) + p50.pick_up_tip() + + if rack == tip200: + if tt_200 == 0 and not reuse: # If this is the first column of tip box and these aren't reused tips + if len(Available_on_deck_slots) > 0: + """ + If there are open deck slots --> need to add a new tip box before pickup + """ + p200_rack_count += 1 + tt_200 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_200ul", Available_on_deck_slots[0], f"200 ul Tip Rack #{p200_rack_count}" + ) + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count} to slot {Available_on_deck_slots[0]}") + Available_on_deck_slots.pop(0) + p200_racks_ondeck.append(addtiprack) + p200_racks_to_dump.append(addtiprack) + p200.tip_racks.append(addtiprack) + elif len(Available_on_deck_slots) == 0 and len(Available_off_deck_slots) > 0: + p200_rack_count += 1 + tt_200 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_200ul", Available_off_deck_slots[0], f"200 ul Tip Rack #{p200_rack_count}" + ) + Available_off_deck_slots.pop( + 0 + ) # Load rack into staging area slot to be moved on deck- want this slot removed so we know when we need manual addition + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count}") + p200_racks_offdeck.append(addtiprack) # used in TipSwap then deleted once it is moved + p200.tip_racks.append(addtiprack) # lets pipette know it can use this rack now + TipSwap(200) # Throw first tip box out and replace with a box from staging area + elif ( + len(Available_on_deck_slots) == 0 and len(Available_off_deck_slots) == 0 + ): # If there are no tip racks on deck or in staging area to use + ctx.pause("Please place a new 200ul Tip Rack in slot B4") + p200_rack_count += 1 + tt_200 += 12 + addtiprack = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B4", f"200 ul Tip Rack #{p200_rack_count}") + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count}") + p200_racks_offdeck.append(addtiprack) # used in TipSwap, then deleted once it is moved + p200.tip_racks.append(addtiprack) # lets pipette know it can use this rack now + TipSwap(200) # Throw first tip box out and replace with a box from staging area + # Call where tips will actually be picked up + if reuse and REUSE_REMOVE_TIPS: + p200.pick_up_tip(tip200_reuse.wells()[8 * reuse_col]) + else: + tt_200 -= 1 + ctx.comment("Column " + str(12 - tt_200)) + ctx.comment("Available On Deck Slots:" + str(len(Available_on_deck_slots))) + ctx.comment("Available Off Deck Slots:" + str(len(Available_off_deck_slots))) + p200.pick_up_tip() + + def TipSwap(tipvol): + + if tipvol == 50: + rack_to_dispose = p50_racks_to_dump[0] + rack_to_add = p50_racks_offdeck[0] + deck_slot = p50_racks_to_dump[0].parent + old_deck_slot = p50_racks_offdeck[0].parent + + p50_racks_ondeck.append(rack_to_add) + p50_racks_to_dump.pop(0) + p50_racks_to_dump.append(rack_to_add) + p50_racks_ondeck.pop(0) + p50_racks_offdeck.pop(0) + + if tipvol == 200: + rack_to_dispose = p200_racks_to_dump[0] + rack_to_add = p200_racks_offdeck[0] + deck_slot = p200_racks_to_dump[0].parent + old_deck_slot = p200_racks_offdeck[0].parent + + p200_racks_ondeck.append(rack_to_add) + p200_racks_to_dump.pop(0) + p200_racks_to_dump.append(rack_to_add) + p200_racks_ondeck.pop(0) + p200_racks_offdeck.pop(0) + + ctx.move_labware(labware=rack_to_dispose, new_location=trash, use_gripper=USE_GRIPPER) + ctx.move_labware(labware=rack_to_add, new_location=deck_slot, use_gripper=USE_GRIPPER) + ctx.comment(f"Threw out: {rack_to_dispose} and placed {rack_to_add} to {deck_slot}") + + ###################################################################################################### + def run_tag_profile(): + # heater shaker mixing speed and time + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1400) + ctx.delay(Fragmentation_Mix_time) + h_s.deactivate_shaker() + + # Presetting Thermocycler Temps + ctx.comment("****Starting Fragmentation Profile (37C for 10 minutes with 100C lid)****") + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(37) + + # Move Plate to TC + ctx.comment("****Moving Plate to Pre-Warmed TC Module Block****") + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, tc_mod, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + tc_mod.close_lid() + + tc_mod.set_block_temperature(temperature=37, hold_time_minutes=Fragmentation_time, block_max_volume=50) + + tc_mod.open_lid() + + # #Move Plate to H-S + ctx.comment("****Moving Plate off of TC****") + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ################################################################################### + def run_er_profile(): + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1400) + ctx.delay(End_Repair_Mix_time) + h_s.deactivate_shaker() + # Presetting Thermocycler Temps + ctx.comment("****Starting End Repair Profile (65C for 30 minutes with 100C lid)****") + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(65) + + # Move Plate to TC + ctx.comment("****Moving Plate to Pre-Warmed TC Module Block****") + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, tc_mod, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + tc_mod.close_lid() + + tc_mod.set_block_temperature(temperature=65, hold_time_minutes=30, block_max_volume=50) + + tc_mod.deactivate_block() + + tc_mod.open_lid() + + # #Move Plate to H-S + ctx.comment("****Moving Plate off of TC****") + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ##################################################################################### + def run_ligation_profile(): + # Ligration heatersheaker mixing speed and time + ctx.comment("****Mixing Ligation using heatershaker") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1400) + ctx.delay(Ligation_Mix_time) + h_s.deactivate_shaker() + # Presetting Thermocycler Temps + ctx.comment("****Starting Ligation Profile (20C for 15 minutes with 100C lid)****") + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(20) + + # Move Plate to TC + ctx.comment("****Moving Plate to Pre-Warmed TC Module Block****") + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, tc_mod, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + tc_mod.close_lid() + + tc_mod.set_block_temperature(temperature=20, hold_time_minutes=ligation_tc_time, block_max_volume=50) + + tc_mod.deactivate_block() + + tc_mod.open_lid() + + # #Move Plate to H-S + ctx.comment("****Moving Plate off of TC****") + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ################################################################################# + def run_amplification_profile(): + # Ligration heatersheaker mixing speed and time + ctx.comment("****Mixing amplification using heatershaker") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1400) + ctx.delay(amplification_Mix_time) + h_s.deactivate_shaker() + # Presetting Thermocycler Temps + ctx.comment("****Starting Amplification Profile (37C for 5 minutes, then 50C for 5 minutes with 100C lid)****") + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(98) + + # Move Plate to TC + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving Sample Plate onto TC****") + ctx.move_labware(sample_plate_2, tc_mod, use_gripper=USE_GRIPPER) + + ############################################################################################################################################ + if dry_run == False: + tc_mod.set_lid_temperature(105) + tc_mod.close_lid() + if dry_run == False: + profile_PCR_1 = [{"temperature": 98, "hold_time_seconds": 45}] + tc_mod.execute_profile(steps=profile_PCR_1, repetitions=1, block_max_volume=50) + profile_PCR_2 = [ + {"temperature": 98, "hold_time_seconds": 15}, + {"temperature": 60, "hold_time_seconds": 30}, + {"temperature": 72, "hold_time_seconds": 30}, + ] + tc_mod.execute_profile(steps=profile_PCR_2, repetitions=PCRCYCLES, block_max_volume=50) + profile_PCR_3 = [{"temperature": 72, "hold_time_minutes": 1}] + tc_mod.execute_profile(steps=profile_PCR_3, repetitions=1, block_max_volume=50) + tc_mod.set_block_temperature(4) + ############################################################################################################################################ + tc_mod.open_lid() + # Move Sample Plate to H-S + ctx.comment("****Moving Sample Plate back to H-S****") + ctx.move_labware(sample_plate_2, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # get FLP plate out of the way + ctx.comment("****Moving FLP Plate back to TC****") + ctx.move_labware(FLP_plate, tc_mod, use_gripper=USE_GRIPPER) + + ############################################################################################# + def mix_beads(pip, res, vol, reps, col): + # Multiplier tells + mix_vol = (num_cols - col) * vol + if pip == p50: + if mix_vol > 50: + mix_vol = 50 + if pip == p200: + if mix_vol > 200: + mix_vol = 200 + + if res == bead_res: + width = res.width + else: + width = res.diameter + move = (width / 2) - 1 + + loc_center_a = res.bottom().move(types.Point(x=0, y=0, z=0.5)) + loc_center_d = res.bottom().move(types.Point(x=0, y=0, z=0.5)) + loc1 = res.bottom().move(types.Point(x=move, y=0, z=5)) + loc2 = res.bottom().move(types.Point(x=0, y=move, z=5)) + loc3 = res.bottom().move(types.Point(x=-move, y=0, z=5)) + loc4 = res.bottom().move(types.Point(x=0, y=-move, z=5)) + loc5 = res.bottom().move(types.Point(x=move / 2, y=move / 2, z=5)) + loc6 = res.bottom().move(types.Point(x=-move / 2, y=move / 2, z=5)) + loc7 = res.bottom().move(types.Point(x=-move / 2, y=-move / 2, z=5)) + loc8 = res.bottom().move(types.Point(x=move / 2, y=-move / 2, z=5)) + + loc = [loc_center_d, loc1, loc5, loc2, loc6, loc3, loc7, loc4, loc8] + + pip.aspirate(mix_vol, res.bottom().move(types.Point(x=0, y=0, z=10))) # Blow bubbles to start + pip.dispense(mix_vol, loc_center_d) + for x in range(reps): + pip.aspirate(mix_vol, loc_center_a) + pip.dispense(mix_vol, loc[x]) + pip.flow_rate.aspirate = 10 + pip.flow_rate.dispense = 10 + pip.aspirate(mix_vol, loc_center_a) + pip.dispense(mix_vol, loc_center_d) + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 150 + + def remove_supernatant(well, vol, waste_, column): + ctx.comment("-------Removing " + str(vol) + "ul of Supernatant-------") + p200.flow_rate.aspirate = 15 + num_trans = math.ceil(vol / 190) + vol_per_trans = vol / num_trans + for x in range(num_trans): + tiptrack(tip200, column, reuse=True if REUSE_REMOVE_TIPS else False) + p200.aspirate(vol_per_trans / 2, well.bottom(0.2)) + ctx.delay(seconds=1) + p200.aspirate(vol_per_trans / 2, well.bottom(0.2)) + p200.air_gap(10) + p200.dispense(p200.current_volume, waste_) + p200.air_gap(10) + if REUSE_REMOVE_TIPS: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + else: + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + p200.flow_rate.aspirate = 150 + + ################################################################################ + # First Definition- Fragmentation # + ################################################################################ + + def Fragmentation(): + ctx.comment("-------Starting Fragmentation-------") + + for i in range(num_cols): + + ctx.comment("**** Mixing and Transfering beads to column " + str(i + 1) + " ****") + + tiptrack(tip50, None, reuse=False) + # mix_beads(p50,frag_res,frag_vol,7 if i == 0 else 2,i) #5 reps for first mix in reservoir + p50.flow_rate.dispense = 15 + p50.aspirate(frag_vol, frag_res) + p50.dispense(p50.current_volume, samples[i]) + p50.flow_rate.dispense = 150 + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(frag_vol, samples[i].bottom(1)) + p50.dispense(p50.current_volume, samples[i].bottom(5)) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + run_tag_profile() # Heats TC --> moves plate to TC --> TAG Profile --> removes plate from TC + + ################################################################################ + # End-Repair # + ################################################################################ + + def end_repair(): + ctx.comment("-------Starting end_repair-------") + + for i in range(num_cols): + + ctx.comment("**** Mixing and Transfering beads to column " + str(i + 1) + " ****") + + tiptrack(tip50, None, reuse=False) + # mix_beads(p50,er_res,end_repair_vol,7 if i == 0 else 2,i) #5 reps for first mix in reservoir + p50.flow_rate.dispense = 15 + p50.aspirate(end_repair_vol, er_res) + p50.dispense(p50.current_volume, samples[i]) + p50.flow_rate.dispense = 150 + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(end_repair_vol, samples[i].bottom(1)) + p50.dispense(p50.current_volume, samples[i].bottom(5)) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + run_er_profile() # Heats TC --> moves plate to TC --> TAG Profile --> removes plate from TC + + ################################################################################ + # Index Ligation # + ################################################################################ + + def index_ligation(): + ctx.comment("-------Ligating Indexes-------") + ctx.comment("-------Adding and Mixing ELM-------") + for i in samples: + tiptrack(tip50, None, reuse=False) + p50.aspirate(ligation_vol, ligation_res) + p50.dispense(p50.current_volume, i) + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 75 + p50.flow_rate.dispense = 75 + p50.aspirate(ligation_vol - 10, i) + p50.dispense(p50.current_volume, i.bottom(8)) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # Add and mix adapters + ctx.comment("-------Adding and Mixing Adapters-------") + for i, x in zip(samples, adapters): + tiptrack(tip50, None, reuse=False) + p50.aspirate(adapter_vol, x) + p50.dispense(p50.current_volume, i) + for y in range(10 if not dry_run else 1): + if y == 9: + p50.flow_rate.aspirate = 75 + p50.flow_rate.dispense = 75 + p50.aspirate(40, i) + p50.dispense(40, i.bottom(8)) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + + run_ligation_profile() + """ + Moves FLP plate off TC --> Heats TC --> moves sample plate to TC --> ELM Profile --> removes sample plate from TC --> places FLP plate back on TC + """ + + ################################################################################ + # ligation Cleanup # + ################################################################################ + def lib_cleanup(): + ctx.comment("-------Starting Cleanup-------") + ctx.comment("-------Adding and Mixing Cleanup Beads-------") + + # Move FLP plate off magnetic module if it's there + if FLP_plate.parent == magblock: + ctx.comment("****Moving FLP Plate off Magnetic Module****") + ctx.move_labware(FLP_plate, tc_mod, use_gripper=USE_GRIPPER) + + for x, i in enumerate(samples): + tiptrack(tip200, None, reuse=False) + mix_beads(p200, bead_res, bead_vol_1, 7 if x == 0 else 2, x) + p200.aspirate(bead_vol_1, bead_res) + p200.dispense(bead_vol_1, i) + mix_beads(p200, i, bead_vol_1, 7 if not dry_run else 1, num_cols - 1) + for x in range(10 if not dry_run else 1): + if x == 9: + p200.flow_rate.aspirate = 75 + p200.flow_rate.dispense = 75 + p200.aspirate(bead_vol_1, i) + p200.dispense(bead_vol_1, i.bottom(8)) + p200.flow_rate.aspirate = 150 + p200.flow_rate.dispense = 150 + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + ctx.delay(minutes=bead_inc, msg="Please wait " + str(bead_inc) + " minutes while samples incubate at RT.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving Labware to Magnet for Pelleting****") + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=4.5, msg="Time for Pelleting") + + for col, i in enumerate(samples): + remove_supernatant(i, 130, waste1_res, col) + samp_list = samples + + ################################################################################ + # Wash 2 x with 80% Ethanol # + ################################################################################ + p200.flow_rate.aspirate = 75 + p200.flow_rate.dispense = 75 + for y in range(2 if not dry_run else 1): + ctx.comment(f"-------Wash # {y+1} with Ethanol-------") + if y == 0: # First wash + this_res = etoh1_res + this_waste_res = waste1_res + else: # Second Wash + this_res = etoh2_res + this_waste_res = waste2_res + if REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + for i in samp_list: + if not REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + p200.aspirate(150, this_res) + p200.air_gap(10) + p200.dispense(p200.current_volume, i.top()) + ctx.delay(seconds=1) + p200.air_gap(10) + if not REUSE_ETOH_TIPS: + p200.drop_tip() if trash_tips else p200.return_tip() + + ctx.delay(seconds=10) + # Remove the ethanol wash + for x, i in enumerate(samp_list): + if REUSE_ETOH_TIPS: + if x != 0: + tiptrack(tip200, None, reuse=False) + if not REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + p200.aspirate(155, i) + p200.air_gap(10) + p200.dispense(p200.current_volume, this_waste_res) + ctx.delay(seconds=1) + p200.air_gap(10) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + p200.flow_rate.aspirate = 150 + p200.flow_rate.dispense = 150 + + ################################################################################ + # Washes Complete, Move on to Drying Steps # + ################################################################################ + + ctx.delay(minutes=2, msg="Allow 3 minutes for residual ethanol to dry") + + # Return Plate to H-S from Magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving sample plate off of Magnet****") + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ################################################################################ + # Adding RSB and Mixing # + ################################################################################ + + for col, i in enumerate(samp_list): + ctx.comment(f"****Adding RSB to Columns: {col+1}****") + tiptrack(tip50, col, reuse=True if REUSE_RSB_TIPS else False) + p50.aspirate(rsb_vol_1, rsb_res) + p50.air_gap(5) + p50.dispense(p50.current_volume, i) + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(15, i.bottom(1)) + p50.dispense(15, i.bottom(4)) + p50.flow_rate.aspirate = 100 + p50.flow_rate.dispense = 100 + p50.air_gap(5) + if REUSE_RSB_TIPS: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + else: + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + ctx.delay(minutes=3, msg="Allow 3 minutes for incubation and liquid aggregation.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Move Samples to Magnet for Pelleting****") + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=2, msg="Please allow 2 minutes for beads to pellet.") + + p200.flow_rate.aspirate = 10 + for i, (s, e) in enumerate(zip(samp_list, samples_2)): + tiptrack(tip50, i, reuse=True if REUSE_RSB_TIPS else False) + p50.aspirate(elution_vol, s) + p50.air_gap(5) + p50.dispense(p50.current_volume, e.bottom(1), push_out=3) + p50.air_gap(5) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # move new sample plate to D1 or heatersheaker + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving sample plate off of Magnet****") + ctx.move_labware(sample_plate_2, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # Keep Sample PLate 1 to B2 + ctx.comment("****Moving Sample_plate_1 Plate off magnet to B2****") + ctx.move_labware(sample_plate, "B2", use_gripper=USE_GRIPPER) + + ctx.comment("****Moving FLP Plate off TC****") + ctx.move_labware(FLP_plate, magblock, use_gripper=USE_GRIPPER) + + ################################################################################ + # Amplification # + ################################################################################ + + def lib_amplification(): + ctx.comment("-------Starting lib_amplification-------") + + for i in range(num_cols): + + ctx.comment("**** Mixing and Transfering beads to column " + str(i + 1) + " ****") + + tiptrack(tip50, None, reuse=False) + mix_beads(p50, amplification_res, amplification_vol, 7 if i == 0 else 2, i) # 5 reps for first mix in reservoir + p50.flow_rate.dispense = 15 + p50.aspirate(amplification_vol, amplification_res) + p50.dispense(p50.current_volume, samples_2[i]) + p50.flow_rate.dispense = 150 + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(amplification_vol, samples_2[i].bottom(1)) + p50.dispense(p50.current_volume, samples_2[i].bottom(5)) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + run_amplification_profile() # moves plate to TC --> TAG Profile --> removes plate from TC + + ################################################################################ + # Final Cleanup # + ################################################################################ + + def lib_cleanup_2(): + ctx.comment("-------Starting Cleanup-------") + ctx.comment("-------Adding and Mixing Cleanup Beads-------") + for x, i in enumerate(samples_2): + tiptrack(tip200, None, reuse=False) + mix_beads(p200, bead_res, bead_vol_2, 7 if x == 0 else 2, x) + p200.aspirate(bead_vol_2, bead_res) + p200.dispense(bead_vol_2, i) + mix_beads(p200, i, bead_vol_2, 7 if not dry_run else 1, num_cols - 1) + for x in range(10 if not dry_run else 1): + if x == 9: + p200.flow_rate.aspirate = 75 + p200.flow_rate.dispense = 75 + p200.aspirate(bead_vol_2, i) + p200.dispense(bead_vol_2, i.bottom(8)) + p200.flow_rate.aspirate = 150 + p200.flow_rate.dispense = 150 + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + ctx.delay(minutes=bead_inc, msg="Please wait " + str(bead_inc) + " minutes while samples incubate at RT.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving Labware to Magnet for Pelleting****") + ctx.move_labware(sample_plate_2, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=4.5, msg="Time for Pelleting") + + for col, i in enumerate(samples_2): + remove_supernatant(i, 130, waste1_res, col) + samp_list_2 = samples_2 + ################################################################################ + # Wash 2 x with 80% Ethanol # + ################################################################################ + p200.flow_rate.aspirate = 75 + p200.flow_rate.dispense = 75 + for y in range(2 if not dry_run else 1): + ctx.comment(f"-------Wash # {y+1} with Ethanol-------") + if y == 0: # First wash + this_res = etoh1_res + this_waste_res = waste1_res + else: # Second Wash + this_res = etoh2_res + this_waste_res = waste2_res + if REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + for i in samp_list_2: + if not REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + p200.aspirate(150, this_res) + p200.air_gap(10) + p200.dispense(p200.current_volume, i.top()) + ctx.delay(seconds=1) + p200.air_gap(10) + if not REUSE_ETOH_TIPS: + p200.drop_tip() if trash_tips else p200.return_tip() + + ctx.delay(seconds=10) + # Remove the ethanol wash + for x, i in enumerate(samp_list_2): + if REUSE_ETOH_TIPS: + if x != 0: + tiptrack(tip200, None, reuse=False) + if not REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + p200.aspirate(155, i) + p200.air_gap(10) + p200.dispense(p200.current_volume, this_waste_res) + ctx.delay(seconds=1) + p200.air_gap(10) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + p200.flow_rate.aspirate = 150 + p200.flow_rate.dispense = 150 + + ################################################################################ + # Washes Complete, Move on to Drying Steps # + ################################################################################ + + ctx.delay(minutes=3, msg="Allow 3 minutes for residual ethanol to dry") + + # Return Plate to H-S from Magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving sample plate off of Magnet****") + ctx.move_labware(sample_plate_2, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ################################################################################ + # Adding RSB and Mixing # + ################################################################################ + + for col, i in enumerate(samp_list_2): + ctx.comment(f"****Adding RSB to Columns: {col+1}****") + tiptrack(tip50, col, reuse=True if REUSE_RSB_TIPS else False) + p50.aspirate(rsb_vol_2, rsb_res) + p50.air_gap(5) + p50.dispense(p50.current_volume, i) + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(15, i.bottom(1)) + p50.dispense(15, i.bottom(4)) + p50.flow_rate.aspirate = 100 + p50.flow_rate.dispense = 100 + p50.air_gap(5) + if REUSE_RSB_TIPS: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + else: + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + ctx.delay(minutes=3, msg="Allow 3 minutes for incubation and liquid aggregation.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Move Samples to Magnet for Pelleting****") + ctx.move_labware(sample_plate_2, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=2, msg="Please allow 2 minutes for beads to pellet.") + + p200.flow_rate.aspirate = 10 + for i, (s, e) in enumerate(zip(samp_list_2, samples_flp)): + tiptrack(tip50, i, reuse=True if REUSE_RSB_TIPS else False) + p50.aspirate(elution_vol_2, s) + p50.air_gap(5) + p50.dispense(p50.current_volume, e.bottom(1), push_out=3) + p50.air_gap(5) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # Set Block Temp for Final Plate + tc_mod.set_block_temperature(4) + + Fragmentation() + end_repair() + index_ligation() + lib_cleanup() + lib_amplification() + lib_cleanup_2() diff --git a/app-testing/files/protocols/pl_Illumina_DNA_PCR_Free.py b/app-testing/files/protocols/pl_Illumina_DNA_PCR_Free.py new file mode 100644 index 00000000000..df16279af49 --- /dev/null +++ b/app-testing/files/protocols/pl_Illumina_DNA_PCR_Free.py @@ -0,0 +1,1387 @@ +from opentrons import protocol_api +from opentrons import types +import math +import numpy as np + +metadata = {"protocolName": "Illumina PCR-Free DNA Prep", "author": "Zach Galluzzo "} +requirements = {"robotType": "Flex", "apiLevel": "2.18"} +tt_50 = 0 +tt_200 = 0 +p50_rack_count = 0 +p200_rack_count = 0 +tip50 = 50 +tip200 = 200 +reps = 1 +threshold = 1 +p50_racks_ondeck = [] +p200_racks_ondeck = [] +p50_racks_offdeck = [] +p200_racks_offdeck = [] +RemoveEtoh_tip = [] +RemoveSup_tip = [] +RSB_tip = [] + + +def add_parameters(p): + p.add_int( + display_name="Number of Samples", + variable_name="num_samples", + default=8, + minimum=1, + maximum=48, + description="How many samples will be processed (multiples of 8 work most efficiently- 48 sample max)?", + ) + + p.add_bool( + display_name="gDNA", + variable_name="gDNA", + default=True, + description="Will the inputted DNA be gDNA? (If not, the protocol will follow the blood/ saliva input section.)", + ) + + p.add_bool( + display_name="Standard Input", + variable_name="standard_input", + default=True, + description="Should this follow the standard input protocol (>100 ng DNA input)?", + ) + + p.add_bool( + display_name="Use Gripper", + variable_name="USE_GRIPPER", + default=True, + description="Is there a gripper present to move labware around the deck?", + ) + p.add_bool( + display_name="Trash tips", + variable_name="trash_tips", + default=True, + description="True will throw tips in waste chute, false will return them to tip rack.", + ) + p.add_bool( + display_name="Heater-Shaker", + variable_name="heater_shaker", + default=True, + description="Is there a heater-shaker on deck for this protocol?", + ) + p.add_bool( + display_name="On-deck Thermocycler", + variable_name="ondeck_thermo", + default=True, + description="Is there an on-deck thermocycler for this protocol? (must be Gen2)", + ) + p.add_bool( + display_name="Dry Run", + variable_name="dry_run", + default=False, + description="True will shorten incubation times and mix reps, false will run according to manual.", + ) + p.add_bool( + display_name="Reuse Ethanol Tips?", + variable_name="REUSE_ETOH_TIPS", + default=False, + description="If true, tips for ethanol washes will be reused for both washes.", + ) + p.add_bool( + display_name="Reuse RSB/ elution tips?", + variable_name="REUSE_RSB_TIPS", + default=False, + description="If true, tips for RSB transfers and mixes will be reused.", + ) + p.add_bool( + display_name="Reuse supernatant tips?", + variable_name="REUSE_REMOVE_TIPS", + default=False, + description="If true, all supernatant clearances for any single sample will be done by 1 tip.", + ) + p.add_str( + display_name="P1000 Mount", + variable_name="mount1000", + default="left", + description="Which mount is the p1000 pipette on?", + choices=[{"display_name": "right", "value": "right"}, {"display_name": "left", "value": "left"}], + ) + + +def run(ctx): + # Flexible Parameters for Customers to Change from Protocol Library Page + + gDNA = ctx.params.gDNA # False for Blood/ Saliva input + standard_input = ctx.params.standard_input + USE_GRIPPER = ctx.params.USE_GRIPPER + trash_tips = ctx.params.trash_tips + heater_shaker = ctx.params.heater_shaker # Is there a heater-shaker present? + ondeck_thermo = ctx.params.ondeck_thermo # Is there an on-deck thermocycler? + dry_run = ctx.params.dry_run + REUSE_ETOH_TIPS = ctx.params.REUSE_ETOH_TIPS + REUSE_RSB_TIPS = ctx.params.REUSE_RSB_TIPS # Reuse tips for RSB buffer (adding RSB, mixing, and transferring) + REUSE_REMOVE_TIPS = ctx.params.REUSE_REMOVE_TIPS # Reuse tips for supernatant removal + num_samples = ctx.params.num_samples + mount1000 = ctx.params.mount1000 + + # gDNA = True # False for Blood/ Saliva input + # standard_input = True + # USE_GRIPPER = True + # trash_tips = True + # heater_shaker = True # Is there a heater-shaker present? + # ondeck_thermo = True # Is there an on-deck thermocycler? + # dry_run = False + # REUSE_ETOH_TIPS = False + # REUSE_RSB_TIPS = False # Reuse tips for RSB buffer (adding RSB, mixing, and transferring) + # REUSE_REMOVE_TIPS = False # Reuse tips for supernatant removal + # num_samples = 8 + # mount1000 ="left" + + ################################################################################ + # Changing Parameters for troubleshooting (FAS/ Science team) # + ################################################################################ + + tagment = True + tag_cleanup = True + ligate_indexes = True + final_cleanup = True + + report = True # Prints helpful comments to assist in debugging + + """ + These are presented in the order that they are called in the protocol, with tagement being the first + and final cleanup being the last step. In order to ensure a successful run, none of the steps should be skipped. + This means that the list above should never have a True --> False --> True. Once one of these steps is False, + everything below (after) it should be false as well, otherwise there is a very low chance of a successful library prep. + + Depending where in the protocol you stop, setting up the next protocol may be slightly different + (FLP plate, sample plate and tips may differ) so make sure to setup the second version according to the app + (this means potentially rerunning LPC). + + Also, none of the four steps listed above are good stopping points, except completing the final cleanup. + This means this section is only used for troubleshooting, or restarting a protocol after a failure. Samples + should not be stored and come back to the next day in between any of the four steps above. + """ + + ################################################################################ + # Beginning Protocol- Setting Variables # + ################################################################################ + if mount1000 == "left": + mount50 = "right" + else: + mount50 = "left" + + if dry_run: + trash_tips = False + + num_cols = math.ceil(num_samples / 8) + + global reps + global threshold + + # Pre-set parameters + + if standard_input: + if gDNA: + sample_vol = 25 + else: + sample_vol = 30 + cleanup_inc = 2 + + bead_mix_vol = 35 + ipb1_vol = 36 + ipb2_vol = 42 + ipb_vol = ipb1_vol + ipb2_vol + ipb_inc = 2 + rsb_vol = 22 + elution_vol = 20 + tb1_vol = 10 + if gDNA: + blt_v = 15 + blt_vol = blt_v + tb1_vol # 25: blt + tb1 + else: + blt_v = 10 + blt_vol = blt_v + tb1_vol # 20: blt + tb1 for blood/ saliva + + else: + cleanup_inc = 5 + sample_vol = 30 + tb1_vol = 10 + blt_v = 10 + blt_vol = blt_v + tb1_vol + bead_mix_vol = 40 + ipb_vol = 81 + ipb1_vol = ipb_vol + ipb_inc = 5 + rsb_vol = 16 + elution_vol = 14 + + # Same for every condition of this protocol + st2_vol = 10 + twb1_vol = 150 + twb2_vol = 75 + twb_vol = twb1_vol + twb2_vol + elm_vol = 45 + adapter_vol = 5 + hp3_vol = 45 + etoh_vol = 180 + if heater_shaker: + reps = 4 + threshold = 3 + else: + reps = 8 + threshold = 7 + if dry_run: + reps = 1 + + ################################################################################ + # Loading Labware and Instruments # + ################################################################################ + + # Importing Labware, Modules and Instruments + magblock = ctx.load_module("magneticBlockV1", "D2") + temp_mod = ctx.load_module("temperature module gen2", "C1") + temp_adapter = temp_mod.load_adapter("opentrons_96_well_aluminum_block") + temp_plate = temp_adapter.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Temp Module Reservoir Plate") + if not dry_run: + temp_mod.set_temperature(4) + if ondeck_thermo: + tc_mod = ctx.load_module("thermocycler module gen2") + # Just in case + tc_mod.open_lid() + + FLP_plate = magblock.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "FLP Plate") + samples_flp = FLP_plate.rows()[0][:num_cols] + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + # h_s_adapter = h_s.load_adapter('opentrons_96_pcr_adapter') + sample_plate = h_s.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Sample Plate") + # Just in Case + h_s.close_labware_latch() + + else: + sample_plate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "D1", "Sample Pate") + + if standard_input: + samples_lp2 = sample_plate.rows()[0][num_cols : 2 * num_cols] + + liq_samples = sample_plate.rows()[:num_cols] + samples = sample_plate.rows()[0][:num_cols] + reservoir = ctx.load_labware("nest_96_wellplate_2ml_deep", "C2") + + trash = ctx.load_waste_chute() + + # Import Global Variables + + global tip50 + global tip200 + global p50_rack_count + global p200_rack_count + global tt_50 + global tt_200 + global RemoveEtoh_tip + global RemoveSup_tip + global RSB_tip + + p200 = ctx.load_instrument("flex_8channel_1000", mount1000) + p50 = ctx.load_instrument("flex_8channel_50", mount50) + + p50_racks = [] + p200_racks = [] + + Available_on_deck_slots = ["A2", "A3", "B2", "B3", "C3"] + if not ondeck_thermo: + Available_on_deck_slots.append("B1") + Available_off_deck_slots = ["A4", "B4", "C4", "D4"] + p50_racks_to_dump = [] + p200_racks_to_dump = [] + + if REUSE_RSB_TIPS: + Available_on_deck_slots.remove("A3") + tip50_reuse = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "A3") + # RSB_tip = [] + p50_rack_count += 1 + tt_50 += 12 + p50.tip_racks.append(tip50_reuse) + if report: + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count}") + for x in range(num_cols): + RSB_tip.append(tip50_reuse.wells()[8 * x]) + tt_50 -= 1 + p50.starting_tip = tip50_reuse.wells()[(len(RSB_tip)) * 8] + + if REUSE_REMOVE_TIPS: + Available_on_deck_slots.remove("A2") + tip200_reuse = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A2", "200 ul Tip Rack #1") + # RemoveSup_tip = [] + p200_rack_count += 1 + tt_200 += 12 + p200.tip_racks.append(tip200_reuse) + if report: + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count} to slot A2") + for x in range(num_cols): + RemoveSup_tip.append(tip200_reuse.wells()[8 * x]) + tt_200 -= 1 + p200_rack_count += 1 + addtiprack = ctx.load_labware("opentrons_flex_96_tiprack_200ul", Available_on_deck_slots[0], f"200 ul Tip Rack #{p200_rack_count}") + if report: + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count} to slot {Available_on_deck_slots[0]}") + tt_200 += 12 + p200.tip_racks.append(addtiprack) + Available_on_deck_slots.pop(0) + p200_racks_ondeck.append(addtiprack) + p200_racks_to_dump.append(addtiprack) + if not REUSE_ETOH_TIPS: + p200.starting_tip = tip200_reuse.wells()[num_cols * 8] + if REUSE_ETOH_TIPS: + for x in range(num_cols): + RemoveEtoh_tip.append(tip200_reuse.wells()[(8 * num_cols) + (8 * x)]) + tt_200 -= 1 + if num_cols == 6: + p200.starting_tip = addtiprack.wells()[0] + else: + p200.starting_tip = tip200_reuse.wells()[8 * (2 * num_cols)] + if REUSE_ETOH_TIPS and not REUSE_REMOVE_TIPS: + Available_on_deck_slots.remove("A2") + tip200_reuse = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A2", "200 ul Tip Rack #1") + # RemoveEtoh_tip = [] + p200_rack_count += 1 + tt_200 += 12 + p200.tip_racks.append(tip200_reuse) + if report: + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count}") + for x in range(num_cols): + RemoveEtoh_tip.append(tip200_reuse.wells()[8 * x]) + tt_200 -= 1 + p200.starting_tip = tip200_reuse.wells()[num_cols * 8] + + ################################################################################ + # Load Reagent Locations in Reservoirs # + ################################################################################ + """ + colors = ['#008000','#008000','#A52A2A','#00FFFF','#0000FF','#800080',\ + '#ADD8E6','#FF0000','#FFFF00','#FF00FF','#00008B','#7FFFD4',\ + '#FFC0CB','#FFA500','#00FF00','#C0C0C0'] + """ + + ##############Sample Plate############## + sample_liq = ctx.define_liquid(name="Samples", description="DNA sample of known quantity", display_color="#C0C0C0") + for well in sample_plate.wells()[: 8 * num_cols]: + well.load_liquid(liquid=sample_liq, volume=sample_vol) + + ################Cold Res################ + adapters = temp_plate.rows()[0][:num_cols] # used for filling liquids + adapters_ = adapters[0] # used for pipetting into/ out of + adapter_liq = ctx.define_liquid(name="Adapters", description="Adapters to ligate onto DNA insert.", display_color="#A52A2A") + for well in temp_plate.wells()[: 8 * num_cols]: + well.load_liquid(liquid=adapter_liq, volume=adapter_vol * 2) + + elm1 = temp_plate.columns()[num_cols] + elm1_res = elm1[0] + elm_liq = ctx.define_liquid(name="ELM", description="Extension ligation mix", display_color="#00FFFF") + if num_cols >= 4: + elm2 = temp_plate.columns()[num_cols + 1] + elm2_res = elm2[0] + # elm = elm1 + elm2 + for well in elm1: + well.load_liquid(liquid=elm_liq, volume=(elm_vol * 3) * 0.1) + for well in elm2: + well.load_liquid(liquid=elm_liq, volume=(elm_vol * (3 - (6 - num_cols))) * 0.1) + else: + elm = elm1 + for well in elm1: + well.load_liquid(liquid=elm_liq, volume=(elm_vol * num_cols) * 0.1) + + blt = temp_plate.columns()[num_cols + 2] # this is beads (blt) and tb1 mixed in the reservoir + blt_res = blt[0] + blt_liq = ctx.define_liquid(name="BLT", description="Bead linked transposomes", display_color="#008000") + tb1_liq = ctx.define_liquid(name="TB1", description="Tagmentation buffer 1", display_color="#008000") + for well in blt: + well.load_liquid(liquid=blt_liq, volume=(blt_v * num_cols) * 0.1) + well.load_liquid(liquid=tb1_liq, volume=(tb1_vol * num_cols) * 0.1) + + st2 = temp_plate.columns()[num_cols + 3] + st2_res = st2[0] + st2_liq = ctx.define_liquid(name="ST2", description="Stop tagmentation buffer", display_color="#0000FF") + for well in st2: + well.load_liquid(liquid=st2_liq, volume=(st2_vol * num_cols) * 0.1) + + ################Room Temp Res (deepwell)################ + twb = reservoir.columns()[0] + twb_res = twb[0] + twb_liq = ctx.define_liquid(name="TWB", description="Tagmentation wash buffer", display_color="#800080") + for well in twb: + well.load_liquid(liquid=twb_liq, volume=(twb_vol * num_cols) * 0.1) + + hp3 = reservoir.columns()[1] + hp3_res = hp3[0] + hp3_liq = ctx.define_liquid(name="HP3", description="2N Sodium Hydroxide", display_color="#ADD8E6") + for well in hp3: + well.load_liquid(liquid=hp3_liq, volume=(hp3_vol * num_cols) * 0.1) + + ipb = reservoir.columns()[2] + ipb_res = ipb[0] + ipb_liq = ctx.define_liquid(name="IPB", description="Illumina Purification Beads", display_color="#FF0000") + for well in ipb: + well.load_liquid(liquid=ipb_liq, volume=(ipb_vol * num_cols) * 0.1) + + rsb = reservoir.columns()[3] + rsb_res = rsb[0] + rsb_liq = ctx.define_liquid(name="RSB", description="Resuspension buffer", display_color="#FFFF00") + for well in rsb: + well.load_liquid(liquid=rsb_liq, volume=(rsb_vol * num_cols) * 0.1) + + etoh1 = reservoir.columns()[4] + etoh1_res = etoh1[0] + etoh_liq = ctx.define_liquid(name="Ethanol 80%", description="Fresh 80% Ethanol", display_color="#FF00FF") + for well in etoh1: + well.load_liquid(liquid=etoh_liq, volume=(etoh_vol * num_cols) * 0.1) + + etoh2 = reservoir.columns()[5] + etoh2_res = etoh2[0] + for well in etoh2: + well.load_liquid(liquid=etoh_liq, volume=(etoh_vol * num_cols) * 0.1) + + waste1 = reservoir.columns()[-4] + waste1_res = waste1[0] + + waste2 = reservoir.columns()[-3] + waste2_res = waste2[0] + + waste3 = reservoir.columns()[-2] + waste3_res = waste3[0] + + waste4 = reservoir.columns()[-1] + waste4_res = waste4[0] + + ################################################################################ + # Starting to Create Function Definitions # + ################################################################################ + + def tiptrack(rack, reuse_col, reuse=None): + global tt_50 + global tt_200 + global p50_racks_ondeck + global p200_racks_ondeck + global p50_racks_offdeck + global p200_racks_offdeck + global p50_rack_count + global p200_rack_count + global RemoveEtoh_tip + global RemoveSup_tip + global RSB_tip + + if rack == tip50: + if tt_50 == 0 and reuse == None: # If this is the first column of tip box and these aren't reused tips + if len(Available_on_deck_slots) > 0: + # If there are open deck slots --> need to add a new tip box before pickup + p50_rack_count += 1 + tt_50 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_50ul", Available_on_deck_slots[0], f"50 ul Tip Rack #{p50_rack_count}" + ) + if report: + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count} to slot {Available_on_deck_slots[0]}") + Available_on_deck_slots.pop(0) + p50_racks_ondeck.append(addtiprack) + p50_racks_to_dump.append(addtiprack) + p50.tip_racks.append(addtiprack) + elif len(Available_on_deck_slots) == 0 and len(Available_off_deck_slots) > 0: + p50_rack_count += 1 + tt_50 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_50ul", Available_off_deck_slots[0], f"50 ul Tip Rack #{p50_rack_count}" + ) + Available_off_deck_slots.pop( + 0 + ) # Load rack into staging area slot to be moved on deck- want this slot removed so we know when we need manual addition + if report: + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count}") + p50_racks_offdeck.append(addtiprack) # used in TipSwap then deleted once it is moved + p50.tip_racks.append(addtiprack) # lets pipette know it can use this rack now + TipSwap(50) # Throw first tip box out and replace with a box from staging area + elif ( + len(Available_on_deck_slots) == 0 and len(Available_off_deck_slots) == 0 + ): # If there are no tip racks on deck or in staging area to use + ctx.pause("Please place a new 50ul Tip Rack in slot A4") + p50_rack_count += 1 + tt_50 += 12 + addtiprack = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "A4", f"50 ul Tip Rack #{p50_rack_count}") + if report: + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count}") + p50_racks_offdeck.append(addtiprack) # used in TipSwap, then deleted once it is moved + p50.tip_racks.append(addtiprack) # lets pipette know it can use this rack now + TipSwap(50) # Throw first tip box out and replace with a box from staging area + # Call where tips will actually be picked up + if reuse == "RSB" and REUSE_RSB_TIPS: + p50.pick_up_tip(RSB_tip[reuse_col]) + else: + tt_50 -= 1 + if report: + ctx.comment("Column " + str(12 - tt_50)) + ctx.comment("Available On Deck Slots:" + str(len(Available_on_deck_slots))) + ctx.comment("Available Off Deck Slots:" + str(len(Available_off_deck_slots))) + p50.pick_up_tip() + + if rack == tip200: + if tt_200 == 0 and reuse == None: # If this is the first column of tip box and these aren't reused tips + if len(Available_on_deck_slots) > 0: + # If there are open deck slots --> need to add a new tip box before pickup + p200_rack_count += 1 + tt_200 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_200ul", Available_on_deck_slots[0], f"200 ul Tip Rack #{p200_rack_count}" + ) + if report: + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count} to slot {Available_on_deck_slots[0]}") + Available_on_deck_slots.pop(0) + p200_racks_ondeck.append(addtiprack) + p200_racks_to_dump.append(addtiprack) + p200.tip_racks.append(addtiprack) + elif len(Available_on_deck_slots) == 0 and len(Available_off_deck_slots) > 0: + p200_rack_count += 1 + tt_200 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_200ul", Available_off_deck_slots[0], f"200 ul Tip Rack #{p200_rack_count}" + ) + Available_off_deck_slots.pop( + 0 + ) # Load rack into staging area slot to be moved on deck- want this slot removed so we know when we need manual addition + if report: + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count}") + p200_racks_offdeck.append(addtiprack) # used in TipSwap then deleted once it is moved + p200.tip_racks.append(addtiprack) # lets pipette know it can use this rack now + TipSwap(200) # Throw first tip box out and replace with a box from staging area + elif ( + len(Available_on_deck_slots) == 0 and len(Available_off_deck_slots) == 0 + ): # If there are no tip racks on deck or in staging area to use + ctx.pause("Please place a new 200ul Tip Rack in slot B4") + p200_rack_count += 1 + tt_200 += 12 + addtiprack = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B4", f"200 ul Tip Rack #{p200_rack_count}") + if report: + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count}") + p200_racks_offdeck.append(addtiprack) # used in TipSwap, then deleted once it is moved + p200.tip_racks.append(addtiprack) # lets pipette know it can use this rack now + TipSwap(200) # Throw first tip box out and replace with a box from staging area + # Call where tips will actually be picked up + if reuse == "REMOVE" and REUSE_REMOVE_TIPS: + p200.pick_up_tip(RemoveSup_tip[reuse_col]) + elif reuse == "ETOH" and REUSE_ETOH_TIPS: + p200.pick_up_tip(RemoveEtoh_tip[reuse_col]) + else: + tt_200 -= 1 + if report: + ctx.comment( + "Column " + str(12 - tt_200) + " (may be negative in certain conditions- use 12 as a base [i.e -5 = column 7])" + ) + ctx.comment("Available On Deck Slots:" + str(len(Available_on_deck_slots))) + ctx.comment("Available Off Deck Slots:" + str(len(Available_off_deck_slots))) + p200.pick_up_tip() + + def TipSwap(tipvol): + + if tipvol == 50: + rack_to_dispose = p50_racks_to_dump[0] + rack_to_add = p50_racks_offdeck[0] + deck_slot = p50_racks_to_dump[0].parent + old_deck_slot = p50_racks_offdeck[0].parent + + p50_racks_ondeck.append(rack_to_add) + p50_racks_to_dump.pop(0) + p50_racks_to_dump.append(rack_to_add) + p50_racks_ondeck.pop(0) + p50_racks_offdeck.pop(0) + + if tipvol == 200: + rack_to_dispose = p200_racks_to_dump[0] + rack_to_add = p200_racks_offdeck[0] + deck_slot = p200_racks_to_dump[0].parent + old_deck_slot = p200_racks_offdeck[0].parent + + p200_racks_ondeck.append(rack_to_add) + p200_racks_to_dump.pop(0) + p200_racks_to_dump.append(rack_to_add) + p200_racks_ondeck.pop(0) + p200_racks_offdeck.pop(0) + + ctx.move_labware(labware=rack_to_dispose, new_location=trash, use_gripper=USE_GRIPPER) + ctx.move_labware(labware=rack_to_add, new_location=deck_slot, use_gripper=USE_GRIPPER) + if report: + ctx.comment(f"Threw out: {rack_to_dispose} and placed {rack_to_add} to {deck_slot}") + + def run_tag_profile(): + # Presetting Thermocycler Temps + ctx.comment("****Starting TAG Profile (41C for 5 minutes with 100C lid)****") + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(41) + + if heater_shaker: + h_s.deactivate_shaker() + + # Move Plate to TC + ctx.comment("****Moving Plate to Pre-Warmed TC Module Block****") + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, tc_mod if ondeck_thermo else "A1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + tc_mod.close_lid() + + tc_mod.set_block_temperature(temperature=41, hold_time_minutes=5, block_max_volume=50) + + tc_mod.deactivate_block() + + tc_mod.open_lid() + + # #Move Plate to H-S + ctx.comment("****Moving Plate off of TC****") + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def run_elm_profile(): + # get FLP plate out of the way + ctx.comment("****Moving FLP Plate off TC****") + ctx.move_labware(FLP_plate, magblock, use_gripper=USE_GRIPPER) + + # Presetting Thermocycler Temps + ctx.comment("****Starting ELM Profile (37C for 5 minutes, then 50C for 5 minutes with 100C lid)****") + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(37) + + # Move Plate to TC + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving Sample Plate onto TC****") + ctx.move_labware(sample_plate, tc_mod if ondeck_thermo else "A1", use_gripper=USE_GRIPPER) + + tc_mod.close_lid() + + tc_mod.set_block_temperature(temperature=37, hold_time_minutes=5, block_max_volume=50) + tc_mod.set_block_temperature(temperature=50, hold_time_minutes=5, block_max_volume=50) + + tc_mod.deactivate_block() + + tc_mod.open_lid() + + tc_mod.deactivate_lid() + + # Move Plate to H-S + ctx.comment("****Moving Sample Plate back to H-S****") + ctx.move_labware(sample_plate, h_s if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # get FLP plate out of the way + ctx.comment("****Moving FLP Plate back to TC****") + ctx.move_labware(FLP_plate, tc_mod if ondeck_thermo else "A1", use_gripper=USE_GRIPPER) + + def mix_beads(pip, res, vol, reps, col): + """ + pip = which pipette + res = plate/ reservoir well + vol = volume of reagent that is used (will be used to calculate mix volume) + reps = how many pipetting cycles should there be (1 rep = 1 asp/disp cycle + 1 cycle to clear tip) + col = which column are you entering + """ + + # Multiplier tells + mix_vol = (num_cols - col) * vol + if pip == p50: + if mix_vol >= 50: + mix_vol = 40 + if pip == p200: + if mix_vol >= 200: + mix_vol = 185 + + if res == ipb_res: + width = res.width + else: + width = res.diameter + move = (width / 2) - 1.5 + + if report: + ctx.comment(f"Mix Vol = {mix_vol}") + + loc_center_a = res.bottom().move(types.Point(x=0, y=0, z=0.5)) + loc_center_d = res.bottom().move(types.Point(x=0, y=0, z=0.5)) + loc1 = res.bottom().move(types.Point(x=move, y=0, z=5)) + loc2 = res.bottom().move(types.Point(x=0, y=move, z=5)) + loc3 = res.bottom().move(types.Point(x=-move, y=0, z=5)) + loc4 = res.bottom().move(types.Point(x=0, y=-move, z=5)) + loc5 = res.bottom().move(types.Point(x=move / 2, y=move / 2, z=5)) + loc6 = res.bottom().move(types.Point(x=-move / 2, y=move / 2, z=5)) + loc7 = res.bottom().move(types.Point(x=-move / 2, y=-move / 2, z=5)) + loc8 = res.bottom().move(types.Point(x=move / 2, y=-move / 2, z=5)) + + loc = [loc_center_d, loc1, loc5, loc2, loc6, loc3, loc7, loc4, loc8] + if reps > 9: + loc = loc + loc + + for x in range(reps): + pip.aspirate(mix_vol, loc_center_a) + pip.dispense(mix_vol, loc[x]) + pip.flow_rate.aspirate = 10 + pip.flow_rate.dispense = 10 + pip.aspirate(mix_vol, loc_center_a) + pip.dispense(mix_vol, loc_center_d) + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 150 + + def remove_supernatant(well, vol, waste_, column): + ctx.comment("-------Removing " + str(vol) + "ul of Supernatant-------") + p200.flow_rate.aspirate = 15 + num_trans = math.ceil(vol / 190) + vol_per_trans = vol / num_trans + for x in range(num_trans): + tiptrack(tip200, column, reuse="REMOVE" if REUSE_REMOVE_TIPS else None) + p200.aspirate(vol_per_trans / 2, well.bottom(0.4)) + ctx.delay(seconds=1) + p200.aspirate(vol_per_trans / 2, well.bottom(0.4)) + p200.air_gap(10) + p200.dispense(p200.current_volume, waste_.top(-5)) + p200.air_gap(10) + if REUSE_REMOVE_TIPS: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + else: + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + p200.flow_rate.aspirate = 150 + + ################################################################################ + # First Definition- Tagmentation # + ################################################################################ + + def tagmentation(): + global reps + global threshold + ctx.comment("-------Starting Tagmentation-------") + + for i in range(num_cols): + + ctx.comment("**** Mixing and Transfering beads to column " + str(i + 1) + " ****") + + tiptrack(tip50, None, reuse=None) + mix_beads(p50, blt_res, blt_vol, 7 if i == 0 else 2, i) # 7 reps for first mix in reservoir + p50.aspirate(blt_vol, blt_res, rate=0.2) + p50.dispense(p50.current_volume, samples[i], rate=0.2) + for x in range(reps): + p50.aspirate(bead_mix_vol, samples[i].bottom(1)) + p50.dispense(p50.current_volume, samples[i].bottom(5)) + p50.aspirate(bead_mix_vol - 10, samples[i].bottom(3), rate=0.2 if x == threshold else 1) + p50.dispense(p50.current_volume, samples[i].bottom(1), rate=0.2 if x == threshold else 1) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1600) + ctx.delay( + minutes=2 if not dry_run else 0.25, + msg="Please allow 2 minutes to mix beads \ + and tagmentation buffer with the sample. Shaking at 1600 rpm.", + ) + h_s.deactivate_shaker() + if ondeck_thermo: + run_tag_profile() # Heats TC --> moves plate to TC --> TAG Profile --> removes plate from TC + else: + if heater_shaker: + h_s.open_labware_latch() + ctx.pause( + "Place plate on off-deck thermocycler and run pre-programmed TAG profile. Return to Heater-Shaker/ D1 and press resume." + ) + if heater_shaker: + h_s.close_labware_latch() + + ################################################################################ + # Tagmentation Cleanup # + ################################################################################ + + def post_tag_cleanup(): + global reps + global threshold + ctx.comment("-------Post-Tagmentation Cleanup-------") + + for i in range(num_cols): + ctx.comment("**** Adding Stop Solution to Column " + str(i + 1) + " to Stop Tagmentation ****") + + tiptrack(tip50, None, reuse=None) + p50.aspirate(st2_vol, st2_res.bottom().move(types.Point(x=1.5, y=0, z=0.5)), rate=0.3) + p50.dispense(st2_vol, samples[i].bottom(1)) + for x in range(reps): + p50.aspirate(45, samples[i].bottom(1)) + p50.dispense(45, samples[i].bottom(8)) + p50.aspirate(30, samples[i].bottom(3), rate=0.1 if x == threshold else 1) + p50.dispense(30, samples[i].bottom(1), rate=0.1 if x == threshold else 1) + p50.air_gap(5) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1600) + ctx.delay( + minutes=2 if not dry_run else 0.25, + msg="Please allow 2 minutes for stop solution to mix with sample.\ + Shaking at 1600 rpm.", + ) + h_s.deactivate_shaker() + + # move elution plate off magnet before moving sample plate there + ctx.comment("****Moving Elution Plate from Magnet to TC****") + ctx.move_labware(FLP_plate, tc_mod if ondeck_thermo else "A1", use_gripper=USE_GRIPPER) + + # Move Plate to (now empty) Magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving Sample Plate to Magnet****") + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # Settling time Timer + for stopi in np.arange(2 if not dry_run else 0.5, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(stopi) + " minutes left in this incubation.") + + for col, i in enumerate(samples): + remove_supernatant(i, 70, waste1_res, col) + + # Return Plate to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.comment("-------Adding TWB-------") + tiptrack(tip200, None, reuse=None) + for x, i in enumerate(samples): + if x != 0: + p200.dispense(20, twb_res.top(-1)) + p200.aspirate(twb1_vol + 10, twb_res, rate=0.3) + p200.flow_rate.aspirate = 15 + p200.air_gap(10) + p200.flow_rate.aspirate = 150 + # p200.dispense(p200.current_volume,i.top().move(types.Point(x=(i.diameter/2)-1.5,y=0,z=1.5)),rate=0.5) + p200.dispense(twb1_vol + 10, i.top(2), rate=0.3) + ctx.delay(seconds=0.5) + p200.flow_rate.aspirate = 15 + p200.air_gap(10) + p200.flow_rate.aspirate = 150 + if heater_shaker: + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + h_s.set_and_wait_for_shake_speed(1200) + ctx.delay(minutes=2, msg="Shaking for 2 minutes at 1200 rpm.") + h_s.deactivate_shaker() + + else: + for n, i in enumerate(samples): + if n != 0: + tiptrack(tip200, None, reuse=None) + for x in range(10 if not dry_run else 1): + p200.aspirate(120, i.bottom(1), rate=0.4 if x == 9 else 1) + p200.dispense(120, i.bottom(8), rate=0.4 if x == 9 else 1) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # Settling Time timer + for twbi in np.arange(2 if not dry_run else 0.5, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(twbi) + " minutes left in this incubation.") + + for col, i in enumerate(samples): + remove_supernatant(i, 160, waste1_res, col) + + # Return Plate to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ################################################################################ + # Index Ligation # + ################################################################################ + + def index_ligation(): + global reps + global threshold + ctx.comment("-------Ligating Indexes-------") + tiptrack(tip200, None, reuse=None) + for i, well in enumerate(samples): + p200.aspirate(elm_vol, elm1_res if i < 3 else elm2_res, rate=0.2) + p200.dispense(elm_vol, well.top(2), push_out=2, rate=0.2) + if i == num_cols - 1: + p200.air_gap(5) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + ctx.comment("-------Adding and Mixing Adapters-------") + for num, (i, x) in enumerate(zip(samples, adapters)): + tiptrack(tip50, None, reuse=None) + p50.aspirate(adapter_vol, x, rate=0.35) + p50.aspirate(elm_vol - adapter_vol, i.bottom().move(types.Point(x=1, y=0, z=4)), rate=0.25) + p50.dispense(p50.current_volume, i.bottom().move(types.Point(x=1, y=0, z=4)), rate=0.25) + for x in range(reps): + p50.flow_rate.aspirate = 25 + p50.flow_rate.dispense = 25 + p50.aspirate(elm_vol - 10, i) + p50.dispense(p50.current_volume, i.bottom().move(types.Point(x=1.2, y=0, z=8))) + p50.aspirate(elm_vol - 10, i.bottom(1)) + p50.dispense(p50.current_volume, i.bottom().move(types.Point(x=-1.2, y=0, z=3))) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1350) + ctx.delay( + minutes=2 if not dry_run else 0.25, + msg="Please allow 2 minutes \ + for ELM and Adapters to mix with sample. Shaking at 1600 rpm.", + ) + h_s.deactivate_shaker() + if ondeck_thermo: + run_elm_profile() + else: + if heater_shaker: + h_s.open_labware_latch() + ctx.pause( + "Place plate on off-deck thermocycler and run pre-programmed ELM profile. Place FLP plate in A1. Return sample plate to Heater-Shaker/ D1 and press resume." + ) + if heater_shaker: + h_s.close_labware_latch() + """ + Moves FLP plate off TC --> Heats TC --> moves sample plate to TC --> ELM Profile --> removes sample plate from TC --> places FLP plate back on TC + """ + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1200) + ctx.delay(minutes=1) + h_s.deactivate_shaker() + h_s.open_labware_latch() + ctx.comment("****Moving Sample Plate to Magnet for Pelleting****") + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=2, msg="Please allow 2 minutes for beads to pellet") + + for col, i in enumerate(samples): + remove_supernatant(i, 60, waste2_res, col) + + # Return Plate to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving Plate back to Heater-Shaker for Wash****") + ctx.move_labware(sample_plate, h_s if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # Add Tag Wash Buffer + ctx.comment("-------Adding TWB-------") + tiptrack(tip200, None, reuse=None) + for i in samples: + p200.dispense(p200.current_volume, twb_res.top(-1)) + p200.aspirate(twb2_vol + 10, twb_res, rate=0.3) + p200.flow_rate.aspirate = 15 + p200.air_gap(10) + p200.flow_rate.aspirate = 150 + # p200.dispense(p200.current_volume,i.top().move(types.Point(x=(i.diameter/2)-1.5,y=0,z=1.5)),rate=0.5) + p200.dispense(twb2_vol + 10, i.top(1), rate=0.3) + ctx.delay(seconds=0.5) + p200.flow_rate.aspirate = 15 + p200.air_gap(10) + p200.flow_rate.aspirate = 150 + if heater_shaker: + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + h_s.set_and_wait_for_shake_speed(1600) + ctx.delay( + minutes=2 if not dry_run else 0.25, + msg="Please allow 2 minutes for \ + tagmentation wash buffer to mix with sample. Shaking at 1600 rpm.", + ) + h_s.deactivate_shaker() + + else: + for n, i in enumerate(samples): + if n != 0: + tiptrack(tip200, None, reuse=None) + for x in range(10 if not dry_run else 1): + p200.aspirate(75, i, rate=0.2 if x == 9 else 1) + p200.dispense(75, i.bottom(8), rate=0.2 if x == 9 else 1) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # Move Plate to Magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving Sample Plate to Magnet for Pelleting****") + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # Settling Time Timer + for washi in np.arange(2 if not dry_run else 0.5, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in this incubation.") + + for col, i in enumerate(samples): + remove_supernatant(i, 80, waste2_res, col) + + ctx.delay(minutes=2, msg="Pause for 2 minutes to allow residual liquid to dry") + + # Return Plate to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Removing Sample Plate from Magnet****") + ctx.move_labware(sample_plate, h_s if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.comment("-------Adding and Mixing HP3-------") + for i in samples: + tiptrack(tip50, None, reuse=None) + p50.aspirate(hp3_vol, hp3_res) + p50.dispense(p50.current_volume, i) + for x in range(reps): + p50.aspirate(35, i) + p50.dispense(35, i.bottom(8)) + p50.aspirate(30, i.bottom(1), rate=0.25 if x == threshold else 0.5) + p50.dispense(30, i, rate=0.25 if x == threshold else 0.5) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1600) + ctx.delay(minutes=2, msg="Incubate for 2 minutes at RT") + if heater_shaker: + h_s.deactivate_shaker() + + ################################################################################ + # Final Cleanup # + ################################################################################ + + def lib_cleanup(): + global reps + global threshold + ctx.comment("-------Starting Cleanup-------") + ctx.comment("-------Adding and Mixing Cleanup Beads-------") + + for x, i in enumerate(samples): + if standard_input: + tiptrack(tip200, None, reuse=None) + mix_beads( + p200, ipb_res, 40, 7 if x == 0 else 2, num_cols - 1 + ) # num_cols-1 means there will be no calculation of volume, just use exact volume specified + p200.aspirate(ipb1_vol, ipb_res, rate=0.3) + p200.dispense(ipb1_vol, i.bottom(2), push_out=2, rate=0.5) + mix_beads(p200, i, ipb1_vol, 7 if not dry_run else 1, num_cols - 1) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + else: + tiptrack(tip200, None, reuse=None) + mix_beads(p200, ipb_res, ipb1_vol, 7 if x == 0 else 2, num_cols - 1) + p200.aspirate(ipb1_vol, ipb_res, rate=0.3) + p200.dispense(ipb1_vol, i.bottom(2), push_out=2, rate=0.5) + mix_beads(p200, i, ipb1_vol, 7 if not dry_run else 1, num_cols - 1) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1600) + ctx.delay(minutes=ipb_inc, msg="Please wait " + str(ipb_inc) + " minutes while samples incubate at RT.") + if heater_shaker: + h_s.deactivate_shaker() + + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving Labware to Magnet for Pelleting****") + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + if not standard_input: + pelleting_time = 4.5 + else: + if num_cols <= 2: + pelleting_time = 4.5 + elif 2 < num_cols <= 4: + pelleting_time = 4 + elif 4 < num_cols <= 6: + pelleting_time = 3.5 + + ctx.delay(minutes=pelleting_time, msg="Time for Pelleting") + + if standard_input: + ctx.comment("-------Mixing Beads and Transferring to LP2 Wells-------") + # Add beads to LP2 wells + tiptrack(tip200, None, reuse=None) # Only 1 tip needed to transfer to clean wells + for x, i in enumerate(samples_lp2): + mix_beads(p200, ipb_res, 42, 7 if x == 0 else 1, x) + p200.aspirate(ipb2_vol, ipb_res, rate=0.5) + p200.dispense(p200.current_volume, i.bottom(1), push_out=2, rate=0.5) + ctx.delay(seconds=1) + p200.blow_out() + p200.air_gap(5) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # Transfer Supernatant to LP2 wells + for i, (s, l) in enumerate(zip(samples, samples_lp2)): + tiptrack(tip200, None, reuse=None) + p200.aspirate(76, s.bottom(0.6), rate=0.15) + p200.air_gap(10) + p200.dispense(p200.current_volume, l, rate=0.15) + p200.air_gap(10) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # Move plate off magnet to allow bead mixing + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + ctx.comment("-------Mixing Beads in LP2 Wells with LP1 Supernatant-------") + # Mix beads in new wells with supernatant from first well + for n, x in enumerate(samples_lp2): + tiptrack(tip200, None, reuse=None) + mix_beads(p200, x, 42, 8 if not dry_run else 1, n) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1600) + ctx.delay(minutes=2, msg="Allow 2 minutes for samples to incubate at RT") + if heater_shaker: + h_s.deactivate_shaker() + + # Move plate back to magnet for pelleting + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=5 if not dry_run else 0.25) + + for col, i in enumerate(samples_lp2): + remove_supernatant(i, 110, waste2_res, col) # leaving 8 ul to make sure no beads are taken --> + # the extra will be removed in ethanol wash + samp_list = samples_lp2 + + else: + ctx.delay(minutes=0.5) + for col, i in enumerate(samples): + remove_supernatant(i, 118, waste2_res, col) # leaving 8 ul to make sure no beads are taken --> + # the extra will be removed in ethanol wash + samp_list = samples + ################################################################################ + # Wash 2 x with 80% Ethanol # + ################################################################################ + for y in range(2 if not dry_run else 1): + ctx.comment(f"-------Wash # {y+1} with Ethanol-------") + if y == 0: # First wash + this_res = etoh1_res + this_waste_res = waste3_res + else: # Second Wash + this_res = etoh2_res + this_waste_res = waste4_res + tiptrack(tip200, None, reuse=None) # Always multi-dispense with ethanol tips + for i in samp_list: + p200.aspirate(10, this_res.top(4)) + p200.aspirate(etoh_vol, this_res, rate=0.3) + p200.flow_rate.aspirate = 15 + p200.air_gap(10) + p200.flow_rate.aspirate = 150 + p200.dispense(p200.current_volume, i.top().move(types.Point(x=(i.diameter / 2) - 1.5, y=0, z=3)), rate=0.6) + p200.drop_tip() if trash_tips else p200.return_tip() + + # Remove the ethanol wash + for x, i in enumerate(samp_list): + tiptrack(tip200, x if REUSE_ETOH_TIPS else None, reuse="ETOH" if REUSE_ETOH_TIPS else None) + p200.aspirate(100, i.bottom(3), rate=0.1) + ctx.delay(seconds=0.5) + p200.aspirate(100, i, rate=0.1) + p200.dispense(p200.current_volume, this_waste_res.top(-1)) + ctx.delay(seconds=0.5) + p200.blow_out() + + if REUSE_ETOH_TIPS: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + else: + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + p200.flow_rate.aspirate = 150 + p200.flow_rate.dispense = 150 + + ################################################################################ + # Washes Complete --> Clear liquid + Drying Steps # + ################################################################################ + + ctx.delay(minutes=1, msg="Allow 1 minute for ethanol aggregation and drying") + + for i in samp_list: + tiptrack(tip50, None, reuse=None) + p50.aspirate(20, i.bottom(0.5), rate=0.1) + p50.dispense(p50.current_volume, waste4_res) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # Return Plate to H-S from Magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Moving sample plate off of Magnet****") + ctx.move_labware(sample_plate, h_s if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ################################################################################ + # Adding RSB and Mixing # + ################################################################################ + + for col, i in enumerate(samp_list): + ctx.comment(f"****Adding RSB to Columns: {col+1}****") + tiptrack(tip50, col if REUSE_RSB_TIPS else None, reuse="RSB" if REUSE_RSB_TIPS else None) + p50.aspirate(rsb_vol, rsb_res, rate=0.4) + p50.dispense(p50.current_volume, i) + mix_beads(p50, i, elution_vol, reps if not dry_run else 1, num_cols - 1) + if REUSE_RSB_TIPS: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + else: + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1600) + ctx.delay(minutes=2, msg="Allow 2 minutes for (shaking) incubation.") + if heater_shaker: + h_s.deactivate_shaker() + + ctx.delay(minutes=1, msg="Allow 1 minute for liquid aggregation.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.comment("****Move Samples to Magnet for Pelleting****") + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=2, msg="Please allow 2 minutes for beads to pellet.") + + p50.flow_rate.aspirate = 10 + for i, (s, e) in enumerate(zip(samp_list, samples_flp)): + tiptrack(tip50, i if REUSE_RSB_TIPS else None, reuse="RSB" if REUSE_RSB_TIPS else None) + p50.aspirate(elution_vol, s) + p50.air_gap(5) + p50.dispense(p50.current_volume, e.bottom(1), push_out=2) + p50.air_gap(5) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + if ondeck_thermo: + # Set Block Temp for Final Plate + tc_mod.set_block_temperature(4) + + if tagment: + tagmentation() + if tag_cleanup: + post_tag_cleanup() + if ligate_indexes and not tag_cleanup: + ctx.move_labware(FLP_plate, tc_mod if ondeck_thermo else "A1", use_gripper=USE_GRIPPER) + if ligate_indexes: + index_ligation() + if final_cleanup and not ligate_indexes: + ctx.move_labware(FLP_plate, tc_mod if ondeck_thermo else "A1", use_gripper=USE_GRIPPER) + if final_cleanup: + lib_cleanup() diff --git a/app-testing/files/protocols/pl_Illumina_DNA_Prep_48x_v8.py b/app-testing/files/protocols/pl_Illumina_DNA_Prep_48x_v8.py new file mode 100644 index 00000000000..e7d244aee61 --- /dev/null +++ b/app-testing/files/protocols/pl_Illumina_DNA_Prep_48x_v8.py @@ -0,0 +1,1885 @@ +from opentrons import protocol_api +from opentrons import types +import math + +metadata = { + "protocolName": "Illumina DNA Prep 48x v8", + "author": "Opentrons ", + "source": "Protocol Library", +} +requirements = { + "robotType": "Flex", + "apiLevel": "2.18", +} + + +def add_parameters(parameters): + # ======================== RUNTIME PARAMETERS ======================== + parameters.add_bool(display_name="Dry Run", variable_name="DRYRUN", default=False, description="Whether to perform a dry run or not.") + parameters.add_int( + display_name="Sample Column count", + variable_name="COLUMNS", + default=3, + minimum=1, + maximum=6, + description="How many sample columns to process.", + ) + parameters.add_int( + display_name="PCR Cycles", + variable_name="PCRCYCLES", + default=4, + minimum=1, + maximum=12, + description="How many PCR Cycles to for amplification.", + ) + parameters.add_bool( + display_name="Tip Mixing", + variable_name="TIP_MIX", + default=False, + description="Whether or not to use Tip Mixing instead of a shaker", + ) + parameters.add_bool( + display_name="On Deck Thermo Module", + variable_name="ONDECK_THERMO", + default=True, + description="Whether or not to have an on deck Thermocycler", + ) + parameters.add_str( + display_name="Tip Setting", + variable_name="TIP_SETTING", + default="Single Tip Use", + description="Tip Settings", + choices=[ + {"display_name": "Reusing Tips", "value": "Reusing Tips"}, + {"display_name": "Single Tip Use", "value": "Single Tip Use"}, + {"display_name": "None", "value": "None"}, + ], + ) + + +def run(protocol: protocol_api.ProtocolContext): + # ======================== DOWNLOADED PARAMETERS ======================== + global USE_GRIPPER # T/F Whether or not Using the Gripper + global STP_50_TIPS # T/F Whether or not there are p50 Single Tip Pickups + global STP_200_TIPS # T/F Whether or not there are p200 Single Tip Pickups + global REUSE_ANY_50_TIPS # T/F Whether or not Reusing any p50 + global REUSE_ANY_200_TIPS # T/F Whether or not Reusing any p200 + global TIP_TRASH # T/F whether or not the Tips are Returned + global COLUMNS # Number of Columns of Samples + global PLATE_STACKED # Number of Plates Stacked in Stacked Position + global p50_TIPS # Number of p50 tips currently available + global p200_TIPS # Number of p200 tips currently available + global p50_RACK_COUNT # Number of current total p50 racks + global p200_RACK_COUNT # Number of current total p200 racks + global tiprack_200_STP # Tiprack for p200 Single Tip Pickup + global tiprack_200_STR # Tiprack for p200 Single Tip Return + global tiprack_50_STP # Tiprack for p50 Single Tip Pickup + global tiprack_50_STR # Tiprack for p50 Single Tip Return + global tiprack_50_R # Tiprack for p50 Reuse + global tiprack_200_R1 # Tiprack for p200 Reuse #1 + global tiprack_200_R2 # Tiprack for p200 Reuse #2 + global WASTEVOL # Number - Total volume of Discarded Liquid Waste + global ETOHVOL # Number - Total volume of Available EtOH + + DRYRUN = protocol.params.DRYRUN + COLUMNS = protocol.params.COLUMNS + PCRCYCLES = protocol.params.PCRCYCLES + TIP_MIX = protocol.params.TIP_MIX + ONDECK_THERMO = protocol.params.ONDECK_THERMO + TIP_SETTING = protocol.params.TIP_SETTING + + # ================================================================================================= + # ====================================== ADVANCED PARAMETERS ====================================== + # ================================================================================================= + # -------PROTOCOL STEP------- + STEP_TAG = True # Default True | True = Performs Tagmentation step, False = Skips + STEP_WASH = True # Default True | True = Performs Tagmentation Washes through EPM Resuspension, False = Skips + STEP_PCRDECK = True # Default True | True = Performs Thermocycling step, False = Skips + STEP_CLEANUP = True # Default True | True = Performs Bead cleanup step, False = Skips + # --------------------------- + # This notifies the user that for 5-6 columns (from more than 32 samples up to 48 samples) it requires Tip reusing in order to remain walkaway. + # This setting will override any Runtime parameter, and also pauses to notify the user. So if the user enters 6 columns with Single Tip Use, it will pause and warn that it has to change to Reusing tips in order to remain walkaway. + # Note that if omitting steps (i.e. skipping the last cleanup step) it is possible to do single use tips, but may vary on case by case basis. + # Note that it is also possible to use advanced settings to include pauses that requires user intervention to replenish tipracks, making allowing a run of single Use Tips. + AllSteps = [STEP_TAG, STEP_WASH, STEP_PCRDECK, STEP_CLEANUP] + if COLUMNS == 5 or COLUMNS == 6 and all(AllSteps) == True: + protocol.pause("MUST REUSE TIPS") + TIP_SETTING = "Reusing Tips" + + TIP_TRASH = True # Default True | True = Used tips go in Trash, False = Used tips go back into rack + DEACTIVATE_TEMP = True # Default True | True = Temp and / or Thermocycler deactivate at end of run, False = remain on, such as leaving at 4 degrees + TRASH_POSITION = "CHUTE" # Default 'CHUTE' | 'BIN' or 'CHUTE' + ONDECK_HEATERSHAKER = True # Default True | True = On Deck Heater Shaker, False = No heatershaker and increased tip mixing reps. + ONDECK_TEMP = True # Default True | True = On Deck Temperature module, False = No Temperature Module + USE_GRIPPER = True # Default True | True = Uses the FLEX Gripper, False = No Gripper Movement, protocol pauses and requires manual intervention. + HOTSWAP = False # Default False | True = Allows replenishing tipracks on the off deck positions so the protocol can continue, False = Won't, protocol will most likely have out of tip error message. + HOTSWAP_PAUSE = False # Default False | True = Protocol pauses for replenishing the offdeck tip racks or to continue, False = Protocol won't cause, user must add tipracks at their discretion. + SWAPOFFDECK = False # Default False | True = Protocol will use an empty deck position as a temprorary place to swap new and used tip racks between on and off deck, instead of discarding in the chute, False = won't, and used tipracks will go into the chute. Use True if there is deck space to spare and when doing troubleshooting so tips aren't being discarded with the tip racks. + CUSTOM_OFFSETS = False # Default False | True = use per instrument specific offsets, False = Don't use any offsets. This is for per instrument, per module gripper alignment adjustments that need fine tuning without gripper recalibration. + RES_TYPE_96x = False # Default False | True = use a 96x2ml deepwell for the Reagent Reservoir to keep tips compartmentalized, False = 12x15ml Reservoir. + WASH_AirMultiDis = False # Default False | When adding WASH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + ETOH_AirMultiDis = False # Default False | When adding EtOH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + REUSE_50_TIPS_RSB = False # Default False | Reusing p50 tips + REUSE_200_TIPS_WASH = False # Default False | Reusing p200 tips + REUSE_200_TIPS_ETOH = False # Default False | Reusing p200 tips + STP_200_TIPS = False # Default False | Single Tip Pickup p200 tips + STP_50_TIPS = False # Default False | Single tip Pickup p50 tips + NOLABEL = True # Default False | True = Do no include Liquid Labeling, False = Liquid Labeling is included, adds additional lines to Protocol Step Preview at end of protocol. + REPORT = ( + False # Default False | True = Include Extra Comments in the Protocol Step Preview for troubleshooting, False = Do Not Include + ) + + # ============================== SETTINGS =============================== + if DRYRUN == True: + TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack + DEACTIVATE_TEMP = True # Whether or not to deactivate the heating and cooling modules after a run + REPORT = True # Whether or not to include Extra Comments for Debugging + if TIP_SETTING == "Reusing Tips": + RES_TYPE_96x = True # Type of Reservoir, if reusing tips or omitting rows, set True to use a 96x2ml deepwell + WASH_AirMultiDis = True # When adding WASH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + ETOH_AirMultiDis = True # When adding EtOH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + REUSE_50_TIPS_RSB = True # Reusing p50 tips + REUSE_200_TIPS_WASH = True # Reusing p200 tips + REUSE_200_TIPS_ETOH = True # Reusing p200 tips + if TIP_SETTING == "Single Tip Use": + RES_TYPE_96x = False # Type of Reservoir, if reusing tips or omitting rows, set True to use a 96x2ml deepwell + WASH_AirMultiDis = False # When adding WASH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + ETOH_AirMultiDis = False # When adding EtOH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + REUSE_50_TIPS_RSB = False # Reusing p50 tips + REUSE_200_TIPS_WASH = False # Reusing p200 tips + REUSE_200_TIPS_ETOH = False # Reusing p200 tips + + # ======================== BACKGROUND PARAMETERS ======================== + p50_TIPS = 0 # Number of p50 tips currently available + p200_TIPS = 0 # Number of p50 tips currently available + RESETCOUNT = 0 # Number of times the protocol was paused to reset tips + p50_RACK_COUNT = 0 # Number of current total p50 racks + p200_RACK_COUNT = 0 # Number of current total p200 racks + WASTEVOL = 0 # Number - Total volume of Discarded Liquid Waste + ETOHVOL = 0 # Number - Total volume of Available EtOH + PLATE_STACKED = 0 # Number of Plates Stacked in Stacked Position + REUSE_50_TIPS_COUNT = 0 + REUSE_ANY_50_TIPS = False + if REUSE_50_TIPS_RSB == True: + REUSE_ANY_50_TIPS = True + REUSE_50_TIPS_COUNT += COLUMNS + REUSE_200_TIPS_COUNT = 0 + REUSE_ANY_200_TIPS = False + if REUSE_200_TIPS_WASH == True: + REUSE_ANY_200_TIPS = True + REUSE_200_TIPS_COUNT += COLUMNS + if REUSE_200_TIPS_ETOH == True: + REUSE_ANY_200_TIPS = True + REUSE_200_TIPS_COUNT += COLUMNS + + # =============================== PIPETTE =============================== + p1000 = protocol.load_instrument("flex_8channel_1000", "left") + p50 = protocol.load_instrument("flex_8channel_50", "right") + p1000_flow_rate_aspirate_default = 200 + p1000_flow_rate_dispense_default = 200 + p1000_flow_rate_blow_out_default = 400 + p50_flow_rate_aspirate_default = 50 + p50_flow_rate_dispense_default = 50 + p50_flow_rate_blow_out_default = 100 + + # ================================ LISTS ================================ + p50_RACKS_PIPET = [] # Pipette List + p200_RACKS_PIPET = [] # Pipette List + AVAILABLE_POS_ONDECK = [] # List of Available Positions ON DECK + AVAILABLE_POS_OFFDECK = [] # List of Available Positions OFF DECK + RACKS_TO_DUMP = [] # List of Emptied Racks ON DECK + p50_RACKS_ONDECK = [] # List of P50 Racks ON DECK + p50_RACKS_OFFDECK = [] # List of P50 Racks OFF DECK + p50_RACKS_DROPPED = [] # List of P50 Racks DROPPED + p200_RACKS_ONDECK = [] # List of P200 Racks ON DECK + p200_RACKS_OFFDECK = [] # List of P200 Racks OFF DECK + p200_RACKS_DROPPED = [] # List of P200 Racks DROPPED + SWAPSPOT = [] # List of Next Available Position for SWAP + REUSE_50_TIPS = [] # List of Next Available Position for SWAP + p50_INITIALTIPS = [] # List of Next Available Position for SWAP + REUSE_200_TIPS_1 = [] # List of Next Available Position for SWAP + REUSE_200_TIPS_2 = [] # List of Next Available Position for SWAP + p200_INITIALTIPS = [] # List of Next Available Position for SWAP + + def DefinePosition(tiptype, position, status): + # A Function that is called for potential tip Rack Position. Rather than defining tipracks at the beginning this function is called for each potential tip rack position, values are passed + # to the function and the tip position is added to the appropriate list as, Single Tip Pickup (STP), Reusable Tips, of left as OPEN which can be filled with p50 or p200 as needed. + global STP_50_TIPS + global STP_200_TIPS + global COLUMNS + global REUSE_ANY_50_TIPS + global REUSE_ANY_200_TIPS + global p50_RACK_COUNT + global p200_RACK_COUNT + global tiprack_200_STP + global tiprack_200_STR + global tiprack_50_R + global tiprack_200_R1 + global tiprack_200_R2 + if status == "OPEN": + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "STP_200" and STP_200_TIPS == True: + tiprack_200_STP = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_STP") + for i in range(1, 13): + STP_200_list_x4.append(tiprack_200_STP[f"E{i}"]) + STP_200_list_x4.append(tiprack_200_STP[f"A{i}"]) + rows = ["H", "G", "F", "E", "D", "C", "B", "A"] + columns = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] + for col in columns: + for row in rows: + STP_200_list_x1.append(tiprack_200_STP[row + col]) + if status == "STR_200" and STP_200_TIPS == True: + tiprack_200_STR = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_STR") + for i in range(1, 13): + STR_200_list.append(tiprack_200_STR[f"A{i}"]) + STR_200_list.append(tiprack_200_STR[f"E{i}"]) + if status == "STP_50" and STP_50_TIPS == True: + tiprack_50_STP = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_STP") + for i in range(1, 13): + STP_50_list_x4.append(tiprack_50_STP[f"E{i}"]) + STP_50_list_x4.append(tiprack_50_STP[f"A{i}"]) + rows = ["H", "G", "F", "E", "D", "C", "B", "A"] + columns = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] + for col in columns: + for row in rows: + STP_50_list_x1.append(tiprack_50_STP[row + col]) + if status == "STR_50" and STP_50_TIPS == True: + tiprack_50_STR = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_STR") + for i in range(1, 13): + STR_50_list.append(tiprack_50_STR[f"A{i}"]) + STR_50_list.append(tiprack_50_STR[f"E{i}"]) + if status == "REUSE_50_TIPS" and REUSE_ANY_50_TIPS == True: + p50_RACK_COUNT += 1 + tiprack_50_R = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_R") + protocol.comment(f"Adding tiprack_50_R") + for X in range(COLUMNS): + REUSE_50_TIPS.append(tiprack_50_R.wells_by_name().get(f"A{X+1}", None)) + for X in range(COLUMNS, 12): + p50_INITIALTIPS.append(tiprack_50_R.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_50_TIPS" and REUSE_ANY_50_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "REUSE_200_1TIPS" and REUSE_ANY_200_TIPS == True: + p200_RACK_COUNT += 1 + tiprack_200_R1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_R1") + protocol.comment(f"Adding tiprack_200_R1") + for X in range(COLUMNS): + REUSE_200_TIPS_1.append(tiprack_200_R1.wells_by_name().get(f"A{X+1}", None)) + for X in range(COLUMNS, 12): + p200_INITIALTIPS.append(tiprack_200_R1.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_200_1TIPS" and REUSE_ANY_200_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "REUSE_200_2TIPS" and REUSE_ANY_200_TIPS == True: + p200_RACK_COUNT += 1 + tiprack_200_R2 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_R2") + protocol.comment(f"Adding tiprack_200_R2") + for X in range(COLUMNS * 2): + REUSE_200_TIPS_2.append(tiprack_200_R2.wells_by_name().get(f"A{X+1}", None)) + for X in range(COLUMNS * 2, 12): + p200_INITIALTIPS.append(tiprack_200_R2.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_200_2TIPS" and REUSE_ANY_200_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + + def TipCheck(tiptype, tipuse, rep, tipset): + # A Function that is called replacing the Tip Pickup Step. Values are passed to the function such as tip type, reuse, and which rep (loop number) it is on. + # 1). Checks if there are 0 spaces available, if so, it clears the off deck positions allowing it to be refilled + # 2). Then, if its not Single Tip Pickup (which has its own separate list), if there are no positions available, and not a Reusing step, it adds a rack to either the On or Off Deck + # 3). If it is an Off Deck Position, it automatically starts the TipRackSwap Function, removing the next in line empry rack and swapping in the newly added Off Deck rack + global p50_TIPS + global p200_TIPS + global p50_RACK_COUNT + global p200_RACK_COUNT + global RESETCOUNT + global TIPDONEMODE + if len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) == 0: + if HOTSWAP == True: + for loop, X in enumerate(OFFDECK_LIST): + del protocol.deck[X] + if HOTSWAP_PAUSE == True: + protocol.pause("CLEARING OFFDECK POSITIONS") + else: + protocol.comment("CLEARING OFFDECK POSITIONS") + AVAILABLE_POS_OFFDECK.append(X) + if tiptype == 50 and tipuse != "STP": + if p50_TIPS == 0 and len(p50_INITIALTIPS) == 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + if len(p50_RACKS_ONDECK) == 0: + protocol.comment("FIRST RACK") + else: + protocol.comment("IS THERE AN EMPTY RACK?") + RACKS_TO_DUMP.append(p50_RACKS_ONDECK[0]) + p50_RACKS_ONDECK.pop(0) + p50_RACK_COUNT += 1 + p50_TIPS += 12 + protocol.comment(f"Adding tiprack_50_{p50_RACK_COUNT}") + if len(AVAILABLE_POS_ONDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_50ul", AVAILABLE_POS_ONDECK[0], f"tiprack_50_{p50_RACK_COUNT}" + ) + AVAILABLE_POS_ONDECK.pop(0) + p50_RACKS_ONDECK.append(addtiprack) + p50_RACKS_PIPET.append(addtiprack) + p50.tip_racks = p50_RACKS_PIPET + elif len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_50ul", AVAILABLE_POS_OFFDECK[0], f"tiprack_50_{p50_RACK_COUNT}" + ) + AVAILABLE_POS_OFFDECK.pop(0) + p50_RACKS_OFFDECK.append(addtiprack) + p50_RACKS_PIPET.append(addtiprack) + p50.tip_racks = p50_RACKS_PIPET + TipRackSwap(50) + if p50_TIPS == 0 and len(p50_INITIALTIPS) > 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + p50.pick_up_tip(p50_INITIALTIPS[0]) + p50_INITIALTIPS.pop(0) + if p50_TIPS > 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + if len(p50_INITIALTIPS) > 0: + p50.pick_up_tip(p50_INITIALTIPS[0]) + p50_INITIALTIPS.pop(0) + else: + p50_TIPS -= 1 + p50.pick_up_tip() + if tiptype == 200 and tipuse != "STP": + if p200_TIPS == 0 and len(p200_INITIALTIPS) == 0: + if tipuse == "REUSE" and tipset == "WASH" and REUSE_200_TIPS_WASH == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + elif tipuse == "REUSE" and tipset == "ETOH" and REUSE_200_TIPS_ETOH == True: + p1000.pick_up_tip(REUSE_200_TIPS_2[rep]) + else: + if len(p200_RACKS_ONDECK) == 0: + protocol.comment("FIRST RACK") + else: + protocol.comment("IS THERE AN EMPTY RACK?") + RACKS_TO_DUMP.append(p200_RACKS_ONDECK[0]) + p200_RACKS_ONDECK.pop(0) + p200_RACK_COUNT += 1 + p200_TIPS += 12 + protocol.comment(f"Adding tiprack_200_{p200_RACK_COUNT}") + if len(AVAILABLE_POS_ONDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_200ul", AVAILABLE_POS_ONDECK[0], f"tiprack_200_{p200_RACK_COUNT}" + ) + AVAILABLE_POS_ONDECK.pop(0) + p200_RACKS_ONDECK.append(addtiprack) + p200_RACKS_PIPET.append(addtiprack) + p1000.tip_racks = p200_RACKS_PIPET + elif len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_200ul", AVAILABLE_POS_OFFDECK[0], f"tiprack_200_{p200_RACK_COUNT}" + ) + AVAILABLE_POS_OFFDECK.pop(0) + p200_RACKS_OFFDECK.append(addtiprack) + p200_RACKS_PIPET.append(addtiprack) + p1000.tip_racks = p200_RACKS_PIPET + TipRackSwap(200) + if p200_TIPS == 0 and len(p200_INITIALTIPS) > 0: + if tipuse == "REUSE" and REUSE_ANY_200_TIPS == True: + if tipset == "WASH" and REUSE_200_TIPS_WASH == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + elif tipset == "ETOH" and REUSE_200_TIPS_ETOH == True: + p1000.pick_up_tip(REUSE_200_TIPS_2[rep]) + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + else: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + if p200_TIPS > 0: + if tipuse == "REUSE" and REUSE_ANY_200_TIPS == True: + if tipset == "WASH" and REUSE_200_TIPS_WASH == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + elif tipset == "ETOH" and REUSE_200_TIPS_ETOH == True: + p1000.pick_up_tip(REUSE_200_TIPS_2[rep]) + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + if tiptype == 50 and tipuse == "STP": + p50.pick_up_tip(stp_50_list[rep]) + if tiptype == 200 and tipuse == "STP": + p1000.pick_up_tip(stp_200_list[rep]) + + def TipDone(tiptype, tipuse, rep, tipset): + # A Function that is called replacing the Tip dropping Step. Values are passed to the function such as tip type, reuse, and which rep (loop number) it is on. + # 1). Checks if it is a Single Tip Pickup, Reusable tip, or if the run is a Dry run, + global TIP_TRASH + if tiptype == 50: + if tipuse == "STP": + p50.drop_tip(str_50_list[rep]) if TIP_TRASH == False else p50.drop_tip() + elif tipuse == "REUSE": + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + if tiptype == 200: + if tipuse == "STP": + p1000.drop_tip(str_200_list[rep]) if TIP_TRASH == False else p1000.drop_tip() + elif tipuse == "REUSE": + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + + def TipRackSwap(tiptype): + # A Function that is called from within the TipCheck function to Swap Tip Racks. + # 1). Sets the values within the Function according to the appropriate tip rack list + # 2). If the Global Value of SWAPOFFDECK = True, it will swap tipracks (rather than dump into the Chute) + # 3). First in line of the RACKS_TO_DUMP is the one removed, can either be p50 or p200, no reusable tips or single Tip Racks + if tiptype == 50: + RACK_EMPTY = RACKS_TO_DUMP[0] + RACK_EMPTY_POS = RACKS_TO_DUMP[0].parent + RACK_NEW = p50_RACKS_OFFDECK[0] + RACK_NEW_POS = p50_RACKS_OFFDECK[0].parent + if tiptype == 200: + RACK_EMPTY = RACKS_TO_DUMP[0] + RACK_EMPTY_POS = RACKS_TO_DUMP[0].parent + RACK_NEW = p200_RACKS_OFFDECK[0] + RACK_NEW_POS = p200_RACKS_OFFDECK[0].parent + if SWAPOFFDECK == True: + SWAPSPOT.append(RACK_NEW_POS) + SWAPSPOT.append(RACK_EMPTY_POS) + protocol.comment("EMPTY POS " + str(SWAPSPOT[0])) + protocol.comment("RACK LEAVING THIS OFF DECK POS " + str(SWAPSPOT[1])) + protocol.comment("EMPTY RACK LEAVING THIS POS, MAKING IT THE NEW EMPTY POS " + str(SWAPSPOT[2])) + protocol.move_labware(labware=RACK_NEW, new_location=SWAPSPOT[0], use_gripper=USE_GRIPPER) + protocol.move_labware(labware=RACK_EMPTY, new_location=SWAPSPOT[1], use_gripper=USE_GRIPPER) + SWAPSPOT.pop(0) + SWAPSPOT.pop(0) + if tiptype == 50: + p50_RACKS_ONDECK.append(RACK_NEW) + RACKS_TO_DUMP.pop(0) + p50_RACKS_ONDECK.pop(0) + p50_RACKS_OFFDECK.pop(0) + if tiptype == 200: + p200_RACKS_ONDECK.append(RACK_NEW) + RACKS_TO_DUMP.pop(0) + p200_RACKS_ONDECK.pop(0) + p200_RACKS_OFFDECK.pop(0) + else: + SWAPS_POT = [RACK_EMPTY_POS] + protocol.move_labware(labware=RACK_EMPTY, new_location=TRASH, use_gripper=USE_GRIPPER) + protocol.move_labware(labware=RACK_NEW, new_location=SWAPS_POT[0], use_gripper=USE_GRIPPER) + if tiptype == 50: + p50_RACKS_ONDECK.append(RACK_NEW) + p50_RACKS_DROPPED.append(RACK_EMPTY) + RACKS_TO_DUMP.pop(0) + p50_RACKS_ONDECK.pop(0) + p50_RACKS_OFFDECK.pop(0) + if tiptype == 200: + p200_RACKS_ONDECK.append(RACK_NEW) + p200_RACKS_DROPPED.append(RACK_EMPTY) + RACKS_TO_DUMP.pop(0) + p200_RACKS_ONDECK.pop(0) + p200_RACKS_OFFDECK.pop(0) + + def PlateUnstack(Startposition, Stopposition): + # A Function that creates a plate, grips it based on offsets mimicking the stacked plates height, and moves it to a new position, + # This is a Standin Function until real functionality for plate unstacking is added. + global PLATE_STACKED + if PLATE_STACKED == 7: + sample_plate_1 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_1, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 6: + sample_plate_2 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_2, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 5: + sample_plate_3 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_3, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 4: + sample_plate_4 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_4, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 3: + sample_plate_5 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_5, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 2: + sample_plate_6 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_6, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 1: + sample_plate_7 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_7, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": 0}, + drop_offset=deck_drop_offset, + ) + + # ======================= SIMPLE SETUP ARRANGEMENT ====================== + # This is a condensed, simpler deck layout arrangement based on position. There are 2 sections, one with all the modules on deck (the NGS Workstation setup) and one without. + # This uses the DefinePosition function listed earlier, it asks for: tiptype (None, 50 or 200), position ('A1', etc.), and Status ('OPEN' for any tip, or the special uses as below) + # List all empty positions avaiable. + # DefinePosition(None,'A2','OPEN') <-- Basic, open for either tip type + # DefinePosition(None,'A2','CLOSED') <-- Tip Location is closed, used just for keeping track for the user + # DefinePosition(200,'A2','REUSE_200_1TIPS') <-- Reusable 200 tips + # DefinePosition(200,'A2','STP_200') <-- Single Tip Pickup 200 tips + # DefinePosition(200,'A2','STR_200') <-- Single Tip Return for 200 tips (testing purposes) + # Then there is a block of code for whether or not the trash is a CHUTE or BIN, note that with a BIN position A4 is not available. + + # ========== FIRST ROW =========== + if ONDECK_THERMO == True: + thermocycler = protocol.load_module("thermocycler module gen2") + else: + DefinePosition(None, "A1", "OPEN") + DefinePosition(None, "A2", "OPEN") + # ========== SECOND ROW ========== + if ONDECK_THERMO == True: + pass + else: + pass + DefinePosition(None, "B2", "OPEN") + DefinePosition(50, "B3", "REUSE_50_TIPS") + # ========== THIRD ROW =========== + if ONDECK_TEMP == True: + temp_block = protocol.load_module("temperature module gen2", "C1") + temp_adapter = temp_block.load_adapter("opentrons_96_well_aluminum_block") + reagent_plate_1 = temp_adapter.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Reagent Plate") + else: + reagent_plate_1 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "C1", "Reagent Plate") + reservoir = ( + protocol.load_labware("nest_12_reservoir_15ml", "C2", "Reservoir") + if RES_TYPE_96x == False + else protocol.load_labware("nest_96_wellplate_2ml_deep", "C2") + ) + DefinePosition(200, "C3", "REUSE_200_2TIPS") + # ========== FOURTH ROW ========== + if ONDECK_HEATERSHAKER == True: + heatershaker = protocol.load_module("heaterShakerModuleV1", "D1") + sample_plate_1 = heatershaker.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Sample Plate 1") + else: + sample_plate_1 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "D1", "Sample Plate 1") + mag_block = protocol.load_module("magneticBlockV1", "D2") + CleanupPlate = mag_block.load_labware("nest_96_wellplate_2ml_deep") + # ============ TRASH ============= + if TRASH_POSITION == "BIN": + TRASH = protocol.load_trash_bin("A3") + DefinePosition(200, "D3", "REUSE_200_1TIPS") + if COLUMNS >= 5: + sample_plate_2 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "B4", "Sample Plate 2") + else: + DefinePosition(None, "B4", "OPEN") + DefinePosition(None, "C4", "OPEN") + DefinePosition(None, "D4", "OPEN") + if COLUMNS >= 5: + OFFDECK_LIST = ["C4", "D4"] + else: + OFFDECK_LIST = ["B4", "C4", "D4"] + if TRASH_POSITION == "CHUTE": + TRASH = protocol.load_waste_chute() + DefinePosition(200, "A3", "REUSE_200_1TIPS") + if COLUMNS >= 5: + sample_plate_2 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A4", "Sample Plate 2") + else: + DefinePosition(None, "A4", "OPEN") + DefinePosition(None, "B4", "OPEN") + DefinePosition(None, "C4", "OPEN") + DefinePosition(None, "D4", "OPEN") + if COLUMNS >= 5: + OFFDECK_LIST = ["B4", "C4", "D4"] + else: + OFFDECK_LIST = ["A4", "B4", "C4", "D4"] + + # If SWAPOFFDECK = True (Meaning swapping empty On Deck Racks with New Off Deck Racks), removes the first listed tip position to keep it empty for temporary space. + if SWAPOFFDECK == True: + SWAPSPOT.append(AVAILABLE_POS_ONDECK[0]) + AVAILABLE_POS_ONDECK.pop(0) + + # Reverse the lists of Positions for accessibility (First Checked On Deck Slot is D4, Off Deck is D4) + AVAILABLE_POS_ONDECK.reverse() + AVAILABLE_POS_OFFDECK.reverse() + OFFDECK_LIST.reverse() + + # ========================== REAGENT PLATE_1 ============================ + TAGMIX = reagent_plate_1["A1"] + H20 = reagent_plate_1["A2"] + # EPM = reagent_plate_1['A3'] + # EPM = reagent_plate_1['A4'] + TAGSTOP = reagent_plate_1["A5"] + # + # Barcodes_1 = reagent_plate_1['A7'] + # Barcodes_2 = reagent_plate_1['A8'] + # Barcodes_3 = reagent_plate_1['A9'] + # Barcodes_4 = reagent_plate_1['A10'] + # Barcodes_5 = reagent_plate_1['A11'] + # Barcodes_6 = reagent_plate_1['A12'] + + # ============================ RESERVOIR ================================ + CleanupBead = reservoir["A1"] + RSB = reservoir["A2"] + # + # EtOH = reservoir['A4'] + # EtOH = reservoir['A5'] + # + # TWB = reservoir['A7'] + # TWB = reservoir['A8'] + # + Liquid_trash_well_3 = reservoir["A10"] + Liquid_trash_well_2 = reservoir["A11"] + Liquid_trash_well_1 = reservoir["A12"] + + # ======================= TIP AND SAMPLE TRACKING ======================= + # This is a list of each column to be used in the protocol, as well as any intermediate or final sample positions. + # column_1_list = [f'A{i}' for i in range(1, COLUMNS + 1)] <-- This is a Simple list of 'A1' through 'A12', meaning a full plate. + # Example Protocols can look like this: + # if COLUMNS == 3: + # column_1_list = ['A1','A2','A3'] <-- Initial 3 columns of Samples + # column_2_list = ['A4','A5','A6'] <-- Final 3 columns of Samples + + if COLUMNS == 1: + column_1_list = ["A1"] # Sample Plate 1: input and TAG + column_2_list = ["A1"] # Cleanup Plate: WASH + EPM_list = ["A3"] + TWB_list = ["A7"] + ETOH_list = ["A4"] + column_3_list = ["A5"] # Sample Plate 1: EPM + column_4_list = ["A7"] # Cleanup Plate: ETOH + column_5_list = ["A9"] # Sample Plate 1: Final + barcodes = ["A7"] + if COLUMNS == 2: + column_1_list = ["A1", "A2"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2"] # Cleanup Plate: WASH + EPM_list = ["A3", "A3"] + TWB_list = ["A7", "A7"] + ETOH_list = ["A4", "A4"] + column_3_list = ["A5", "A6"] # Sample Plate 1: EPM + column_4_list = ["A7", "A8"] # Cleanup Plate: ETOH + column_5_list = ["A9", "A10"] # Sample Plate 1: Final + barcodes = ["A7", "A8"] + if COLUMNS == 3: + column_1_list = ["A1", "A2", "A3"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2", "A3"] # Cleanup Plate: WASH + EPM_list = ["A3", "A3", "A3"] + TWB_list = ["A7", "A7", "A7"] + ETOH_list = ["A4", "A4", "A4"] + column_3_list = ["A5", "A6", "A7"] # Sample Plate 1: EPM + column_4_list = ["A7", "A8", "A9"] # Cleanup Plate: ETOH + column_5_list = ["A9", "A10", "A11"] # Sample Plate 1: Final + barcodes = ["A7", "A8", "A9"] + if COLUMNS == 4: + column_1_list = ["A1", "A2", "A3", "A4"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2", "A3", "A4"] # Cleanup Plate: WASH + EPM_list = ["A3", "A3", "A3", "A3"] + TWB_list = ["A7", "A7", "A7", "A7"] + ETOH_list = ["A4", "A4", "A4", "A4"] + column_3_list = ["A5", "A6", "A7", "A8"] # Sample Plate 1: EPM + column_4_list = ["A7", "A8", "A9", "A10"] # Cleanup Plate: ETOH + column_5_list = ["A9", "A10", "A11", "A12"] # Sample Plate 1: Final + barcodes = ["A7", "A8", "A9", "A10"] + if COLUMNS == 5: + column_1_list = ["A1", "A2", "A3", "A4", "A5"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2", "A3", "A4", "A5"] # Cleanup Plate: WASH + EPM_list = ["A3", "A3", "A3", "A4", "A4"] + TWB_list = ["A7", "A7", "A7", "A8", "A8"] + ETOH_list = ["A4", "A4", "A4", "A5", "A5"] + column_3_list = ["A7", "A8", "A9", "A10", "A11"] # Sample Plate 1: EPM + column_4_list = ["A7", "A8", "A9", "A10", "A11"] # Cleanup Plate: ETOH + column_5_list = ["A1", "A2", "A3", "A4", "A5"] # Sample Plate 2: Final + barcodes = ["A7", "A8", "A9", "A10", "A11"] + if COLUMNS == 6: + column_1_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Cleanup Plate: WASH + EPM_list = ["A3", "A3", "A3", "A4", "A4", "A4"] + TWB_list = ["A7", "A7", "A7", "A8", "A8", "A8"] + ETOH_list = ["A4", "A4", "A4", "A5", "A5", "A5"] + column_3_list = ["A7", "A8", "A9", "A10", "A11", "A12"] # Sample Plate 1: EPM + column_4_list = ["A7", "A8", "A9", "A10", "A11", "A12"] # Cleanup Plate: ETOH + column_5_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Sample Plate 2: Final + barcodes = ["A7", "A8", "A9", "A10", "A11", "A12"] + + # ============================ CUSTOM OFFSETS =========================== + # These are Custom Offsets which are a PER INSTRUMENT Setting, to account for slight adjustments of the gripper calibration or labware. + if CUSTOM_OFFSETS == True: + PCRPlate_Z_offset = 0 + Deepwell_Z_offset = 1 + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": -1, "z": 0} + hs_pick_up_offset = {"x": 0, "y": -1, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 1.0, "z": 0.5} + mb_pick_up_offset = {"x": 0, "y": -2, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": -1, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # DECK OFFSETS + deck_drop_offset = {"x": 0, "y": 0, "z": 0} + deck_pick_up_offset = {"x": 0, "y": 0, "z": 0} + else: + PCRPlate_Z_offset = 0 + Deepwell_Z_offset = 0 + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": 0, "z": 0} + hs_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 0.0, "z": 0} + mb_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": 0, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # DECK OFFSETS + deck_drop_offset = {"x": 0, "y": 0, "z": 0} + deck_pick_up_offset = {"x": 0, "y": 0, "z": 0} + + # ================================================================================================= + # ========================================= PROTOCOL START ======================================== + # ================================================================================================= + if ONDECK_THERMO == True: + thermocycler.open_lid() + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + if DRYRUN == False: + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + if ONDECK_THERMO == True: + thermocycler.set_block_temperature(4) + if ONDECK_THERMO == True: + thermocycler.set_lid_temperature(100) + if ONDECK_TEMP == True: + temp_block.set_temperature(4) + protocol.pause("Ready") + if ONDECK_HEATERSHAKER == True: + heatershaker.close_labware_latch() + Liquid_trash = Liquid_trash_well_1 + # ================================================================================================= + # ========================================= PROTOCOL START ======================================== + # ================================================================================================= + + if STEP_TAG == True: + protocol.comment("==============================================") + protocol.comment("--> Tagment") + protocol.comment("==============================================") + + protocol.comment("--> ADDING TAGMIX") + TagVol = 20 + SampleVol = 50 + TagMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + TagPremix = 3 if DRYRUN == False else 1 + TagMix = 6 if TIP_MIX == True else 2 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_1_list): + TipCheck(200, None, loop, None) + p1000.mix(TagPremix, TagVol + 10, TAGMIX.bottom(z=PCRPlate_Z_offset + 1)) + p1000.aspirate(TagVol + 3, TAGMIX.bottom(z=PCRPlate_Z_offset + 0.5), rate=0.25) + p1000.dispense(3, TAGMIX.bottom(z=PCRPlate_Z_offset + 1), rate=0.25) + p1000.dispense(TagVol, sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 1), rate=0.25) + p1000.mix(TagMix, SampleVol, sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 0.75)) + p1000.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(minutes=0.1) + p1000.blow_out(sample_plate_1[X].top(z=-3)) + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.move_to(sample_plate_1[X].top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=5)) + TipDone(200, None, loop, None) + # =============================================== + if ONDECK_HEATERSHAKER == True: + heatershaker.set_and_wait_for_shake_speed(rpm=1600) + protocol.delay(TagMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATERSHAKER TO Thermocycler + if ONDECK_HEATERSHAKER == True and ONDECK_THERMO == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location=thermocycler, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=tc_drop_offset, + ) + heatershaker.close_labware_latch() + if ONDECK_HEATERSHAKER == True and ONDECK_THERMO == False: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_1, + new_location="B1", + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=deck_drop_offset, + ) + heatershaker.close_labware_latch() + if ONDECK_HEATERSHAKER == False and ONDECK_THERMO == True: + protocol.move_labware( + labware=sample_plate_1, + new_location=thermocycler, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=tc_drop_offset, + ) + if ONDECK_HEATERSHAKER == False and ONDECK_THERMO == False: + protocol.move_labware( + labware=sample_plate_1, + new_location="B1", + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + ############################################################################################################################################ + if ONDECK_THERMO == True: + thermocycler.close_lid() + if DRYRUN == False: + profile_TAG = [{"temperature": 55, "hold_time_minutes": 15}] + thermocycler.execute_profile(steps=profile_TAG, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(10) + thermocycler.open_lid() + else: + if DRYRUN == False: + protocol.pause("Pausing to run Tagmentation on an off deck Thermocycler ~15min") + else: + protocol.comment("Pausing to run Tagmentation on an off deck Thermocycler ~15min") + ############################################################################################################################################ + + protocol.comment("--> Adding TAGSTOP") + TAGSTOPVol = 10 + TAGSTOPMixRep = 10 if DRYRUN == False else 1 + TAGSTOPMixVol = 20 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_1_list): + TipCheck(50, None, loop, None) + p50.aspirate(TAGSTOPVol + 3, TAGSTOP.bottom(z=PCRPlate_Z_offset + 1), rate=0.25) + p50.dispense(3, TAGSTOP.bottom(z=PCRPlate_Z_offset + 0.5), rate=0.25) + p50.dispense(TAGSTOPVol, sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 1), rate=0.25) + p50.move_to(sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 0.75)) + p50.mix(TAGSTOPMixRep, TAGSTOPMixVol, sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 0.75)) + p50.blow_out(sample_plate_1[X].top(z=-2)) + TipDone(50, None, loop, None) + # =============================================== + + ############################################################################################################################################ + if ONDECK_THERMO == True: + thermocycler.close_lid() + if DRYRUN == False: + profile_TAGSTOP = [{"temperature": 37, "hold_time_minutes": 15}] + thermocycler.execute_profile(steps=profile_TAGSTOP, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(10) + thermocycler.open_lid() + else: + if DRYRUN == False: + protocol.pause("Pausing to run Tagmentation Stop on an off deck Thermocycler ~15min") + else: + protocol.comment("Pausing to run Tagmentation Stop on an off deck Thermocycler ~15min") + ############################################################################################################################################ + + protocol.comment("--> Transferring Sample to Deepwell") + TransferSup = 50 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_1_list): + TipCheck(200, "REUSE", loop, "WASH") + p1000.aspirate(TransferSup, sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 0.2), rate=0.25) + p1000.dispense(TransferSup, CleanupPlate[column_2_list[loop]].bottom(z=Deepwell_Z_offset + 1)) + TipDone(200, "REUSE", loop, "WASH") + # =============================================== + + if DRYRUN == False and ONDECK_THERMO == True: + protocol.comment("SETTING THERMO to Room Temp") + thermocycler.set_block_temperature(20) + thermocycler.set_lid_temperature(37) + + if DRYRUN == False: + protocol.delay(minutes=4) + + if STEP_WASH == True: + protocol.comment("==============================================") + protocol.comment("--> Wash") + protocol.comment("==============================================") + + if STEP_TAG == False: + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM HEATERSHAKER TO MAG PLATE + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + ActualRemoveSup = 60 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_2_list): + TipCheck(200, "REUSE", loop, "WASH") + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.75)) + p1000.aspirate(100, rate=0.25) + p1000.move_to(CleanupPlate[X].top(z=-2)) + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=-3)) + p1000.move_to(Liquid_trash.top(z=5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.move_to(Liquid_trash.top(z=5)) + TipDone(200, "REUSE", loop, "WASH") + # =============================================== + + for X in range(3): + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM MAG PLATE TO HEATER SHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Wash ") + TWBMaxVol = 100 + TWBTime = 3 * 60 if DRYRUN == False else 0.1 * 60 + TWBMix = 6 if TIP_MIX == True else 2 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + if WASH_AirMultiDis == True: + TipCheck(200, None, loop, None) + for loop, X in enumerate(column_2_list): + p1000.aspirate(TWBMaxVol + 3, reservoir.wells_by_name()[TWB_list[loop]].bottom(z=Deepwell_Z_offset + 1), rate=0.25) + p1000.dispense(3, reservoir.wells_by_name()[TWB_list[loop]].bottom(z=Deepwell_Z_offset + 1), rate=0.25) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[TWB_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[TWB_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(CleanupPlate[X].top(z=5)) + p1000.dispense(TWBMaxVol) + p1000.move_to(CleanupPlate[X].top(z=1)) + protocol.delay(minutes=0.1) + p1000.blow_out(CleanupPlate[X].top(z=-3)) + p1000.move_to(CleanupPlate[X].top(z=5)) + p1000.move_to(CleanupPlate[X].top(z=0)) + p1000.move_to(CleanupPlate[X].top(z=5)) + TipDone(200, None, loop, None) + else: + for loop, X in enumerate(column_2_list): + TipCheck(200, None, loop, None) + p1000.aspirate(TWBMaxVol + 3, reservoir.wells_by_name()[TWB_list[loop]].bottom(z=Deepwell_Z_offset + 1), rate=0.25) + p1000.dispense(3, reservoir.wells_by_name()[TWB_list[loop]].bottom(z=1), rate=0.25) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[TWB_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[TWB_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 1)) + p1000.dispense(TWBMaxVol, rate=0.25) + p1000.mix(TWBMix, 90, rate=0.5) + p1000.move_to(CleanupPlate[X].top(z=1)) + protocol.delay(minutes=0.1) + p1000.blow_out(CleanupPlate[X].top(z=1)) + p1000.aspirate(20) + TipDone(200, None, loop, None) + # =============================================== + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM HEATER SHAKER TO MAG PLATE + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=3) + + protocol.comment("--> Remove Wash") + TWBMaxVol = 100 + 10 + ActualRemoveSup = 100 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_2_list): + TipCheck(200, "REUSE", loop, "WASH") + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3.5)) + p1000.aspirate(TWBMaxVol, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.2)) + protocol.delay(minutes=0.1) + p1000.aspirate(200 - TWBMaxVol, rate=0.25) + p1000.default_speed = 400 + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(200, Liquid_trash) + p1000.move_to(Liquid_trash.top(z=5)) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=-3)) + p1000.move_to(Liquid_trash.top(z=5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.move_to(Liquid_trash.top(z=5)) + TipDone(200, "REUSE", loop, "WASH") + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual Wash") + ActualRemoveSup = 20 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_2_list): + TipCheck(200, "REUSE", loop, "WASH") + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.2)) + p1000.aspirate(100, rate=0.25) + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(100, Liquid_trash) + p1000.move_to(Liquid_trash.top(z=5)) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=-3)) + p1000.move_to(Liquid_trash.top(z=5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.move_to(Liquid_trash.top(z=5)) + TipDone(200, "REUSE", loop, "WASH") + # =============================================== + + # Add A REUSABLE Tip Rack to the front of the list of Racks to Dump, Meaning that it's done being reused and can be cleared from the Deck. + if REUSE_200_TIPS_WASH == True: + RACKS_TO_DUMP.insert(0, tiprack_200_R1) + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM MAG PLATE TO HEATER SHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Adding EPM") + EPMVol = 40 + EPMMixTime = 3 * 60 if DRYRUN == False else 0.1 * 60 + EPMMixRPM = 2000 + EPMMixVol = 35 + EPMVolCount = 0 + EPMMix = 3 if TIP_MIX == True else 1 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_2_list): + TipCheck(50, None, loop, None) + p50.aspirate(EPMVol + 3, reagent_plate_1.wells_by_name()[EPM_list[loop]].bottom(z=PCRPlate_Z_offset + 1), rate=0.25) + p50.dispense(3, reagent_plate_1.wells_by_name()[EPM_list[loop]].bottom(z=PCRPlate_Z_offset + 1), rate=0.25) + p50.move_to((CleanupPlate.wells_by_name()[X].center().move(types.Point(x=1.3 * 0.8, y=0, z=-4)))) + p50.dispense(EPMMixVol, rate=1) + if TIP_MIX == True: + EPMMix = 10 + if TIP_MIX == False: + EPMMix = 2 + for Mix in range(EPMMix): + p50.move_to(CleanupPlate.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1)) + p50.aspirate(EPMMixVol, rate=1) + p50.move_to((CleanupPlate.wells_by_name()[X].center().move(types.Point(x=0, y=1.3 * 0.8, z=-4)))) + p50.dispense(EPMMixVol, rate=1) + p50.move_to(CleanupPlate.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1)) + p50.aspirate(EPMMixVol, rate=1) + p50.move_to((CleanupPlate.wells_by_name()[X].center().move(types.Point(x=1.3 * -0.8, y=0, z=-4)))) + p50.dispense(EPMMixVol, rate=1) + p50.move_to(CleanupPlate.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1)) + p50.aspirate(EPMMixVol, rate=1) + p50.move_to((CleanupPlate.wells_by_name()[X].center().move(types.Point(x=0, y=1.3 * -0.8, z=-4)))) + p50.dispense(EPMMixVol, rate=1) + p50.move_to(CleanupPlate.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1)) + p50.aspirate(EPMMixVol, rate=1) + p50.dispense(EPMMixVol, rate=1) + p50.blow_out(CleanupPlate.wells_by_name()[X].center()) + p50.move_to(CleanupPlate.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 0.3)) + p50.move_to(CleanupPlate.wells_by_name()[X].top(z=5)) + p50.move_to(CleanupPlate.wells_by_name()[X].top(z=0)) + p50.move_to(CleanupPlate.wells_by_name()[X].top(z=5)) + TipDone(50, None, loop, None) + # =============================================== + if TIP_MIX == False: + heatershaker.close_labware_latch() + heatershaker.set_and_wait_for_shake_speed(rpm=EPMMixRPM) + protocol.delay(EPMMixTime) + heatershaker.deactivate_shaker() + + protocol.comment("--> Adding Barcodes") + BarcodeVol = 10 + BarcodeMixRep = 3 if DRYRUN == False else 1 + BarcodeMixVol = 10 + TransferSup = 50 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # ======== DISPENSE =========== + for loop, X in enumerate(column_2_list): + TipCheck(50, None, loop, None) + + protocol.comment("--> Transfer samples") + p50.aspirate(TransferSup, CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.2), rate=0.25) + p50.dispense(TransferSup, sample_plate_1[column_3_list[loop]].bottom(z=PCRPlate_Z_offset + 1)) + + protocol.comment("--> Adding Barcodes") + p50.aspirate(BarcodeVol + 1, reagent_plate_1.wells_by_name()[barcodes[loop]].bottom(z=PCRPlate_Z_offset + 0.3), rate=0.25) + p50.dispense(1, reagent_plate_1.wells_by_name()[barcodes[loop]].bottom(z=PCRPlate_Z_offset + 0.3), rate=0.25) + p50.dispense(BarcodeVol, sample_plate_1.wells_by_name()[column_3_list[loop]].bottom(z=PCRPlate_Z_offset + 1)) + p50.mix(BarcodeMixRep, BarcodeMixVol) + TipDone(50, None, loop, None) + # =============================================== + + if STEP_PCRDECK == True: + ############################################################################################################################################ + if ONDECK_THERMO == True: + if DRYRUN == False: + protocol.comment("SETTING THERMO to Room Temp") + thermocycler.set_block_temperature(4) + thermocycler.set_lid_temperature(100) + + thermocycler.close_lid() + if DRYRUN == False: + profile_PCR_1 = [{"temperature": 68, "hold_time_seconds": 180}, {"temperature": 98, "hold_time_seconds": 180}] + thermocycler.execute_profile(steps=profile_PCR_1, repetitions=1, block_max_volume=50) + profile_PCR_2 = [ + {"temperature": 98, "hold_time_seconds": 45}, + {"temperature": 62, "hold_time_seconds": 30}, + {"temperature": 68, "hold_time_seconds": 120}, + ] + thermocycler.execute_profile(steps=profile_PCR_2, repetitions=PCRCYCLES, block_max_volume=50) + profile_PCR_3 = [{"temperature": 68, "hold_time_minutes": 1}] + thermocycler.execute_profile(steps=profile_PCR_3, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(10) + thermocycler.open_lid() + else: + if DRYRUN == False: + protocol.pause("Pausing to run PCR on an off deck Thermocycler ~20min") + else: + protocol.comment("Pausing to run PCR on an off deck Thermocycler ~20min") + ############################################################################################################################################ + + if STEP_CLEANUP == True: + protocol.comment("==============================================") + protocol.comment("--> Cleanup") + protocol.comment("==============================================") + + protocol.comment("--> TRANSFERRING AND ADDING CleanupBead (0.8x)") + H20Vol = 40 + CleanupBeadVol = 45 + SampleVol = 45 + CleanupBeadMixRPM = 1800 + CleanupBeadMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + CleanupBeadPremix = 3 if DRYRUN == False else 1 + CleanupBeadMix = 6 if TIP_MIX == True else 1 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_3_list): + TipCheck(50, None, loop, None) + + protocol.comment("--> Adding H20") + p50.aspirate(H20Vol + 5, H20.bottom(z=Deepwell_Z_offset), rate=1) + p50.dispense(5, H20.bottom(z=Deepwell_Z_offset), rate=1) + p50.dispense(H20Vol, CleanupPlate[column_4_list[loop]].bottom(z=0.75)) + + protocol.comment("--> Adding Cleanup Beads (0.8x)") + p50.move_to(CleanupBead.bottom(z=Deepwell_Z_offset + 0.75)) + p50.mix(CleanupBeadPremix, CleanupBeadVol) + p50.aspirate(CleanupBeadVol + 3, CleanupBead.bottom(z=Deepwell_Z_offset + 0.75), rate=0.25) + p50.dispense(3, CleanupBead.bottom(z=Deepwell_Z_offset + 0.75), rate=0.5) + p1000.default_speed = 5 + p1000.move_to(CleanupBead.top(z=-3)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(CleanupBead.top().move(types.Point(x=4, z=-3))) + p1000.move_to(CleanupBead.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p50.dispense(CleanupBeadVol, CleanupPlate[column_4_list[loop]].bottom(z=Deepwell_Z_offset + 0.75), rate=1) + protocol.delay(seconds=0.2) + p50.blow_out(CleanupPlate[column_4_list[loop]].top(z=-2)) + + protocol.comment("--> Adding SAMPLE") + p50.aspirate(SampleVol + 3, sample_plate_1[column_3_list[loop]].bottom(z=PCRPlate_Z_offset + 0.75), rate=0.5) + p50.dispense(SampleVol + 3, CleanupPlate[column_4_list[loop]].bottom(z=Deepwell_Z_offset + 0.75), rate=1) + if TIP_MIX == True: + CleanupBeadMix = 10 + if TIP_MIX == False: + CleanupBeadMix = 2 + for Mix in range(CleanupBeadMix): + p50.aspirate(30, rate=0.5) + p50.move_to(CleanupPlate[column_4_list[loop]].bottom(z=Deepwell_Z_offset + 0.25)) + p50.aspirate(20, rate=0.5) + p50.dispense(20, rate=0.5) + p50.move_to(CleanupPlate[column_4_list[loop]].bottom(z=Deepwell_Z_offset + 3)) + p50.dispense(30, rate=0.5) + p50.move_to(CleanupPlate[column_4_list[loop]].top(z=-3)) + protocol.delay(seconds=0.2) + p50.blow_out(CleanupPlate[column_4_list[loop]].top(z=-3)) + TipDone(50, None, loop, None) + # =============================================== + if TIP_MIX == False: + heatershaker.set_and_wait_for_shake_speed(rpm=CleanupBeadMixRPM) + protocol.delay(CleanupBeadMixTime) + heatershaker.deactivate_shaker() + + if DRYRUN == False and ONDECK_THERMO == True: + protocol.comment("SETTING THERMO to Room Temp") + thermocycler.set_block_temperature(20) + thermocycler.set_lid_temperature(37) + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM HEATER SHAKER TO MAG PLATE + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + ActualRemoveSup = 200 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_4_list): + TipCheck(200, "REUSE", loop, "ETOH") + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.75)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate[X].top(z=2)) + p1000.default_speed = 200 + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop, "ETOH") + # =============================================== + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + if ETOH_AirMultiDis == True: + TipCheck(200, None, loop, None) + for loop, X in enumerate(column_4_list): + p1000.aspirate(ETOHMaxVol, reservoir.wells_by_name()[ETOH_list[loop]].bottom(z=Deepwell_Z_offset + 1)) + p1000.move_to(reservoir.wells_by_name()[ETOH_list[loop]].top(z=0)) + p1000.move_to(reservoir.wells_by_name()[ETOH_list[loop]].top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[ETOH_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[ETOH_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(CleanupPlate[X].top(z=2)) + p1000.dispense(ETOHMaxVol, rate=1) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.move_to(CleanupPlate[X].top(z=5)) + p1000.move_to(CleanupPlate[X].top(z=0)) + p1000.move_to(CleanupPlate[X].top(z=5)) + TipDone(200, None, loop, None) + else: + for loop, X in enumerate(column_2_list): + TipCheck(200, None, loop, None) + p1000.aspirate(ETOHMaxVol, reservoir.wells_by_name()[ETOH_list[loop]].bottom(z=Deepwell_Z_offset + 1)) + p1000.move_to(reservoir.wells_by_name()[ETOH_list[loop]].top(z=0)) + p1000.move_to(reservoir.wells_by_name()[ETOH_list[loop]].top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[ETOH_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[ETOH_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(CleanupPlate[X].top(z=-10)) + p1000.dispense(ETOHMaxVol, rate=1) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.move_to(CleanupPlate[X].top(z=5)) + p1000.move_to(CleanupPlate[X].top(z=0)) + p1000.move_to(CleanupPlate[X].top(z=5)) + TipDone(200, None, loop, None) + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + RemoveSup = 160 + ActualRemoveSup = 150 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_4_list): + TipCheck(200, "REUSE", loop, "ETOH") + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.75)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate[X].top(z=2)) + p1000.default_speed = 200 + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(100, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop, "ETOH") + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual Wash") + ActualRemoveSup = 20 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_4_list): + TipCheck(200, "REUSE", loop, "ETOH") + p1000.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 1)) + p1000.aspirate(50, rate=0.25) + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(50, Liquid_trash) + p1000.move_to(Liquid_trash.top(z=5)) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=-3)) + p1000.move_to(Liquid_trash.top(z=5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.move_to(Liquid_trash.top(z=5)) + TipDone(200, "REUSE", loop, "ETOH") + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM MAG PLATE TO HEATER SHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Adding RSB") + RSBVol = 32 + RSBMix = 6 if TIP_MIX == True else 1 + RSBMixRPM = 2000 + RSBMixTime = 1 * 60 if DRYRUN == False else 0.1 * 60 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_4_list): + TipCheck(50, "REUSE", loop, None) + p50.aspirate(RSBVol, RSB.bottom(z=1)) + p50.move_to(CleanupPlate.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1)) + p50.dispense(RSBVol, rate=1) + if TIP_MIX == True: + RSBMix = 10 + if TIP_MIX == False: + RSBMix = 2 + for Mix in range(RSBMix): + p50.aspirate(RSBVol, CleanupPlate.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + p50.dispense(RSBVol, CleanupPlate.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1), rate=1) + p50.blow_out(CleanupPlate.wells_by_name()[X].top(z=-3)) + TipDone(50, "REUSE", loop, None) + # =============================================== + if TIP_MIX == False: + heatershaker.set_and_wait_for_shake_speed(rpm=RSBMixRPM) + protocol.delay(RSBMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE CleanupPlate FROM HEATER SHAKER TO MAG PLATE + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=3) + + if COLUMNS >= 5: + # ============================================================================================ + # GRIPPER MOVE sample_plate_2 FROM Off Deck TO HEATER SHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_2, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=sample_plate_2, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Transferring Supernatant") + TransferSup = 30 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_4_list): + TipCheck(50, "REUSE", loop, None) + p50.move_to(CleanupPlate[X].bottom(z=Deepwell_Z_offset + 0.5)) + p50.aspirate(TransferSup + 1, rate=0.25) + if COLUMNS >= 5: + p50.dispense(TransferSup, sample_plate_2[column_5_list[loop]].bottom(z=PCRPlate_Z_offset + 1)) + else: + p50.dispense(TransferSup, sample_plate_1[column_5_list[loop]].bottom(z=PCRPlate_Z_offset + 1)) + TipDone(50, "REUSE", loop, None) + # =============================================== + + # ================================================================================================= + # ========================================== PROTOCOL END ========================================= + # ================================================================================================= + if DEACTIVATE_TEMP == True: + if ONDECK_THERMO == True: + thermocycler.deactivate_block() + thermocycler.deactivate_lid() + if ONDECK_TEMP == True: + temp_block.deactivate() + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + # ================================================================================================= + # ========================================== PROTOCOL END ========================================= + # ================================================================================================= + + protocol.comment("==============================================") + protocol.comment("--> Report") + protocol.comment("==============================================") + # This is a section that will print out the various lists to help keep track of modifying the protocol, set the REPORT step to False to ignore. + if REPORT == True: + protocol.comment("REUSE_50_TIPS " + str(REUSE_50_TIPS)) + protocol.comment("p50_INITIALTIPS " + str(p50_INITIALTIPS)) + protocol.comment("REUSE_200_TIPS_1 " + str(REUSE_200_TIPS_1)) + protocol.comment("REUSE_200_TIPS_2 " + str(REUSE_200_TIPS_2)) + protocol.comment("p200_INITIALTIPS " + str(p200_INITIALTIPS)) + protocol.comment("SWAPSPOT " + str(SWAPSPOT)) + protocol.comment("AVAILABLE_POS_ONDECK " + str(AVAILABLE_POS_ONDECK)) + protocol.comment("AVAILABLE_POS_OFFDECK " + str(AVAILABLE_POS_OFFDECK)) + protocol.comment("REUSE_50_TIPS_COUNT " + str(REUSE_50_TIPS_COUNT)) + protocol.comment("REUSE_200_TIPS_COUNT " + str(REUSE_200_TIPS_COUNT)) + protocol.comment("p50_RACKS_ONDECK " + str(p50_RACKS_ONDECK)) + protocol.comment("p50_RACKS_OFFDECK " + str(p50_RACKS_OFFDECK)) + protocol.comment("p50_RACKS_DROPPED " + str(p50_RACKS_DROPPED)) + protocol.comment("p50_TIPS " + str(p50_TIPS)) + protocol.comment("p50_RACKS_PIPET " + str(p50_RACKS_PIPET)) + protocol.comment("p200_RACKS_ONDECK " + str(p200_RACKS_ONDECK)) + protocol.comment("p200_RACKS_OFFDECK " + str(p200_RACKS_OFFDECK)) + protocol.comment("p200_RACKS_DROPPED " + str(p200_RACKS_DROPPED)) + protocol.comment("p200_TIPS " + str(p200_TIPS)) + protocol.comment("p200_RACKS_PIPET " + str(p200_RACKS_PIPET)) + protocol.comment("RACKS_TO_DUMP " + str(RACKS_TO_DUMP)) + + # This is a section that is used to define liquids, and label wells, this is optional, and unconnected from the rest of the protocol, used only for the App and Website + # This is at the end because it adds lines of code to the runtime that can be at the end rather than the beginning, since it has no effect on the protocol setps. + if NOLABEL == True: + Sample_Volume = 40 + CleanupBead_Volume = COLUMNS * (45) + TAGSTOP_Volume = COLUMNS * (10) + ETOH_Volume = COLUMNS * (300) + TWB_Volume = COLUMNS * (900) + RSB_Volume = COLUMNS * (32) + TAGMIX_Volume = COLUMNS * (22) + EPM_Volume = COLUMNS * (40) + H20_Volume = COLUMNS * (40) + + TotalColumn = ["A", "B", "C", "D", "E", "F", "G", "H"] + + # ======== DEFINING LIQUIDS ======= + CleanupBead = protocol.define_liquid( + name="EtOH", description="CleanupBead Beads", display_color="#704848" + ) # 704848 = 'CleanupBead Brown' + EtOH = protocol.define_liquid(name="EtOH", description="80% Ethanol", display_color="#9ACECB") # 9ACECB = 'Ethanol Blue' + RSB = protocol.define_liquid(name="RSB", description="Resuspension Buffer", display_color="#00FFF2") # 00FFF2 = 'Base Light Blue' + Liquid_trash_well = protocol.define_liquid( + name="Liquid_trash_well", description="Liquid Trash", display_color="#9B9B9B" + ) # 9B9B9B = 'Liquid Trash Grey' + Sample = protocol.define_liquid(name="Sample", description="Sample", display_color="#52AAFF") # 52AAFF = 'Sample Blue' + TAGSTOP = protocol.define_liquid(name="TAGSTOP", description="Tagmentation Stop", display_color="#FF0000") # FF0000 = 'Base Red' + TWB = protocol.define_liquid(name="TWB", description="Tagmentation Wash Buffer", display_color="#FFA000") # FFA000 = 'Base Orange' + TAGMIX = protocol.define_liquid(name="TAGMIX", description="Tagmentation Mix", display_color="#FFFB00") # FFFB00 = 'Base Yellow' + EPM = protocol.define_liquid(name="EPM", description="EPM", display_color="#0EFF00") # 0EFF00 = 'Base Green' + H20 = protocol.define_liquid(name="H20", description="H20", display_color="#0082FF") # 0082FF = 'Base Blue' + Barcodes = protocol.define_liquid(name="Barcodes", description="Barcodes", display_color="#7DFFC4") # 7DFFC4 = 'Barcode Green' + Final_Sample = protocol.define_liquid( + name="Final_Sample", description="Final Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + Placeholder_Sample = protocol.define_liquid( + name="Placeholder_Sample", description="Placeholder Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + + # ======== LOADING LIQUIDS ======= + if RES_TYPE_96x == "12x15ml": + reservoir.wells_by_name()["A1"].load_liquid(liquid=CleanupBead, volume=CleanupBead_Volume) + reservoir.wells_by_name()["A2"].load_liquid(liquid=RSB, volume=RSB_Volume) + if COLUMNS < 5: + reservoir.wells_by_name()["A4"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()["A7"].load_liquid(liquid=TWB, volume=TWB_Volume) + if COLUMNS == 5: + reservoir.wells_by_name()["A4"].load_liquid(liquid=EtOH, volume=(ETOH_Volume * (3 / 5))) + reservoir.wells_by_name()["A5"].load_liquid(liquid=EtOH, volume=(ETOH_Volume * (2 / 5))) + reservoir.wells_by_name()["A7"].load_liquid(liquid=TWB, volume=(TWB_Volume * (3 / 5))) + reservoir.wells_by_name()["A8"].load_liquid(liquid=TWB, volume=(TWB_Volume * (2 / 5))) + if COLUMNS == 6: + reservoir.wells_by_name()["A4"].load_liquid(liquid=EtOH, volume=(ETOH_Volume * (3 / 6))) + reservoir.wells_by_name()["A5"].load_liquid(liquid=EtOH, volume=(ETOH_Volume * (3 / 6))) + reservoir.wells_by_name()["A7"].load_liquid(liquid=TWB, volume=(TWB_Volume * (3 / 6))) + reservoir.wells_by_name()["A8"].load_liquid(liquid=TWB, volume=(TWB_Volume * (3 / 6))) + reservoir.wells_by_name()["A10"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()["A11"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()["A12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if RES_TYPE_96x == "96x2ml": + for loop, X in enumerate(TotalColumn): + reservoir.wells_by_name()[X + "1"].load_liquid(liquid=CleanupBead, volume=CleanupBead_Volume) + reservoir.wells_by_name()[X + "2"].load_liquid(liquid=RSB, volume=RSB_Volume) + if COLUMNS < 5: + reservoir.wells_by_name()[X + "4"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()[X + "7"].load_liquid(liquid=TWB, volume=TWB_Volume) + if COLUMNS == 5: + reservoir.wells_by_name()[X + "4"].load_liquid(liquid=EtOH, volume=(ETOH_Volume * (3 / 5))) + reservoir.wells_by_name()[X + "5"].load_liquid(liquid=EtOH, volume=(ETOH_Volume * (2 / 5))) + reservoir.wells_by_name()[X + "7"].load_liquid(liquid=TWB, volume=(TWB_Volume * (3 / 5))) + reservoir.wells_by_name()[X + "8"].load_liquid(liquid=TWB, volume=(TWB_Volume * (2 / 5))) + if COLUMNS == 6: + reservoir.wells_by_name()[X + "4"].load_liquid(liquid=EtOH, volume=(ETOH_Volume * (3 / 6))) + reservoir.wells_by_name()[X + "5"].load_liquid(liquid=EtOH, volume=(ETOH_Volume * (3 / 6))) + reservoir.wells_by_name()[X + "7"].load_liquid(liquid=TWB, volume=(TWB_Volume * (3 / 6))) + reservoir.wells_by_name()[X + "8"].load_liquid(liquid=TWB, volume=(TWB_Volume * (3 / 6))) + reservoir.wells_by_name()[X + "10"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()[X + "11"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()[X + "12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if COLUMNS >= 1: + for loop, X in enumerate(TotalColumn): + if COLUMNS < 5: + sample_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + sample_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "1"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 2: + for loop, X in enumerate(TotalColumn): + if COLUMNS < 5: + sample_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "6"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + sample_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "2"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 3: + for loop, X in enumerate(TotalColumn): + if COLUMNS < 5: + sample_plate_1.wells_by_name()[X + "3"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "11"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + sample_plate_1.wells_by_name()[X + "3"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "3"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 4: + for loop, X in enumerate(TotalColumn): + if COLUMNS < 5: + sample_plate_1.wells_by_name()[X + "4"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "12"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + sample_plate_1.wells_by_name()[X + "4"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "4"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "11"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "5"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "11"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 6: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "6"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "12"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "6"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "12"].load_liquid(liquid=Barcodes, volume=5) + for loop, X in enumerate(TotalColumn): + reagent_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=TAGMIX, volume=TAGMIX_Volume) + reagent_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=H20, volume=H20_Volume) + if COLUMNS >= 5: + reagent_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=EPM, volume=EPM_Volume) + if COLUMNS == 5: + reagent_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=EPM, volume=(EPM_Volume * (3 / 5))) + reagent_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=EPM, volume=(EPM_Volume * (2 / 5))) + if COLUMNS == 6: + reagent_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=EPM, volume=(EPM_Volume * (3 / 6))) + reagent_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=EPM, volume=(EPM_Volume * (3 / 6))) + reagent_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=TAGSTOP, volume=TAGSTOP_Volume) diff --git a/app-testing/files/protocols/pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol.py b/app-testing/files/protocols/pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol.py new file mode 100644 index 00000000000..912577f4619 --- /dev/null +++ b/app-testing/files/protocols/pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol.py @@ -0,0 +1,1324 @@ +from opentrons import protocol_api + +metadata = { + "protocolName": "Illumina Stranded Total RNA Prep, Ligation with Ribo-Zero Plus", + "author": "Dandra Howell ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.18", +} + + +def add_parameters(parameters): + parameters.add_int( + display_name="Columns", + variable_name="Columns", + default=1, + minimum=1, + maximum=6, + description="How many columns of the plate will be processed", + ) + + parameters.add_int( + display_name="PCR Cycles", + variable_name="PCR_Cycles", + default=13, + minimum=8, + maximum=20, + description="Number of amplification cycles", + ) + + parameters.add_bool( + display_name="Dry Run", variable_name="DryRun", description="Dry runs will skip incubations on the thermocycler", default=False + ) + + parameters.add_bool( + display_name="On Deck Thermocycler", + variable_name="on_deck_thermo", + description="Will you be using an Opentrons Thermocycler?", + default=True, + ) + + parameters.add_int( + display_name="Column on Index Anchor Plate", + variable_name="Index_Anchor_start", + description="Choose column on the RNA Index Anchor plate that you want to use first", + default=1, + minimum=1, + maximum=12, + ) + + parameters.add_int( + display_name="Column on Index Adapter Plate", + variable_name="Index_Adapter_start", + description="Choose column on the Index Adapter plate that you want to use first", + default=1, + minimum=1, + maximum=12, + ) + + +# Deck Configuration +def run(ctx): + # Parameters + DryRun = ctx.params.DryRun + Columns = ctx.params.Columns + on_deck_thermo = ctx.params.on_deck_thermo + PCR_Cycles = ctx.params.PCR_Cycles + Index_Adapter_start = ctx.params.Index_Anchor_start + Index_Anchor_start = ctx.params.Index_Adapter_start + # Hardware and Consumables + if Columns == 1: + # Hardware and Consumables + temp_block = ctx.load_module("temperature module gen2", "D1") + temp_adapter = temp_block.load_adapter("opentrons_96_well_aluminum_block") + reagent_plate = temp_adapter.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt") + mag_block = ctx.load_module("magneticBlockV1", "A3") + if on_deck_thermo == True: + thermo = ctx.load_module("thermocycler module gen2") + Reservior = ctx.load_labware("nest_96_wellplate_2ml_deep", "A2") + Index_Anchors = ctx.load_labware("eppendorf_96_wellplate_150ul", "A4", "Index Anchors") # Custom labware + chute = ctx.load_waste_chute() + Index_Plate = ctx.load_labware("eppendorf_96_wellplate_150ul", "B4", "Index Plate") # Custom labware + sample_1 = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "C1", "Sample_1") + sample_2 = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "C4", "Sample_2") + + # Pipettes + tip50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "B3", "tip50_1") + tip50_2 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "C3", "tip50_2") + tip50_3 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "D2", "tip50_3") + tip200_1 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B2", "tip200_1") + tip200_2 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "C2", "tip200_2") + tip200_3 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "D4", "tip200_3") + + p50 = ctx.load_instrument("flex_8channel_50", "right", tip_racks=[tip50_1, tip50_2, tip50_3]) + p200 = ctx.load_instrument("flex_8channel_1000", "left", tip_racks=[tip200_1, tip200_2, tip200_3]) + + # Lists for Tip Tracking + p50_on_deck_slots = ["B3", "C3"] + p50_extension_tips = [] + p50_manual_tips = [] + p50_slots = ["B3", "C3", "A4", "B4"] + p50_extension_slots = ["A4", "B4"] + + p200_on_deck_slots = ["B2", "C2"] + p200_extension_tips = [tip200_3] + p200_manual_tips = [] + p200_slots = ["B2", "C2", "D2", "C4", "D4"] + p200_extension_slots = ["C4", "D4"] + else: + temp_block = ctx.load_module("temperature module gen2", "D1") + temp_adapter = temp_block.load_adapter("opentrons_96_well_aluminum_block") + reagent_plate = temp_adapter.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt") + mag_block = ctx.load_module("magneticBlockV1", "A3") + if on_deck_thermo == True: + thermo = ctx.load_module("thermocycler module gen2") + Reservior = ctx.load_labware("nest_96_wellplate_2ml_deep", "A2") + Index_Anchors = ctx.load_labware("eppendorf_96_wellplate_150ul", protocol_api.OFF_DECK, "Index Anchors") # Custom labware + chute = ctx.load_waste_chute() + Index_Plate = ctx.load_labware("eppendorf_96_wellplate_150ul", protocol_api.OFF_DECK, "Index Plate") # Custom labware + sample_1 = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "C1", "Sample_1") + sample_2 = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", protocol_api.OFF_DECK, "Sample_2") + if Columns > 3: + sample_3 = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", protocol_api.OFF_DECK, "Sample_3") + sample_3_as = sample_3.rows()[0][:12] + # Pipettes + tip50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "B3", "tip50_1") + tip50_2 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "C3", "tip50_2") + tip50_3 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "A4", "tip50_3") + tip50_4 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "B4", "tip50_4") + tip50_5 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_5") + if Columns >= 3: + tip50_6 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_6") + tip50_7 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_7") + if Columns >= 4: + tip50_8 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_8") + tip50_9 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_9") + tip50_10 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_10") + if Columns >= 5: + tip50_11 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_11") + tip50_12 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_12") + if Columns == 6: + tip50_13 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_13") + tip50_14 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", protocol_api.OFF_DECK, "tip50_14") + tip200_1 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B2", "tip200_1") + tip200_2 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "C2", "tip200_2") + tip200_3 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "D2", "tip200_3") + tip200_4 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "C4", "tip200_4") + tip200_5 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "D4", "tip200_5") + if Columns >= 3: + tip200_6 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", protocol_api.OFF_DECK, "tip200_6") + tip200_7 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", protocol_api.OFF_DECK, "tip200_7") + if Columns >= 4: + tip200_8 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", protocol_api.OFF_DECK, "tip200_8") + tip200_9 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", protocol_api.OFF_DECK, "tip200_9") + if Columns >= 5: + tip200_10 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", protocol_api.OFF_DECK, "tip200_10") + tip200_11 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", protocol_api.OFF_DECK, "tip200_11") + if Columns == 6: + tip200_12 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", protocol_api.OFF_DECK, "tip200_12") + tip200_13 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", protocol_api.OFF_DECK, "tip200_13") + + if Columns == 2: + p50 = ctx.load_instrument("flex_8channel_50", "right", tip_racks=[tip50_1, tip50_2, tip50_3, tip50_4, tip50_5]) + p200 = ctx.load_instrument("flex_8channel_1000", "left", tip_racks=[tip200_1, tip200_2, tip200_3, tip200_4, tip200_5]) + + # Lists for Tip Tracking + p50_on_deck_slots = ["B3", "C3"] + p50_extension_tips = [tip50_3, tip50_4] + p50_manual_tips = [tip50_5] + p50_slots = ["B3", "C3", "A4", "B4"] + p50_extension_slots = ["A4", "B4"] + + p200_on_deck_slots = ["B2", "C2", "D2"] + p200_extension_tips = [tip200_4, tip200_5] + p200_manual_tips = [] + p200_slots = ["B2", "C2", "D2", "C4", "D4"] + p200_extension_slots = ["C4", "D4"] + + if Columns == 3: + p50 = ctx.load_instrument( + "flex_8channel_50", "right", tip_racks=[tip50_1, tip50_2, tip50_3, tip50_4, tip50_5, tip50_6, tip50_7] + ) + p200 = ctx.load_instrument( + "flex_8channel_1000", "left", tip_racks=[tip200_1, tip200_2, tip200_3, tip200_4, tip200_5, tip200_6, tip200_7] + ) + + # Lists for Tip Tracking + p50_on_deck_slots = ["B3", "C3"] + p50_extension_tips = [tip50_3, tip50_4] + p50_manual_tips = [tip50_5, tip50_6, tip50_7] + p50_slots = ["B3", "C3", "A4", "B4"] + p50_extension_slots = ["A4", "B4"] + + p200_on_deck_slots = ["B2", "C2", "D2"] + p200_extension_tips = [tip200_4, tip200_5] + p200_manual_tips = [tip200_6, tip200_7] + p200_slots = ["B2", "C2", "D2", "C4", "D4"] + p200_extension_slots = ["C4", "D4"] + + if Columns == 4: + p50 = ctx.load_instrument( + "flex_8channel_50", + "right", + tip_racks=[tip50_1, tip50_2, tip50_3, tip50_4, tip50_5, tip50_6, tip50_7, tip50_8, tip50_9, tip50_10], + ) + p200 = ctx.load_instrument( + "flex_8channel_1000", + "left", + tip_racks=[tip200_1, tip200_2, tip200_3, tip200_4, tip200_5, tip200_6, tip200_7, tip200_8, tip200_9], + ) + + # Lists for Tip Tracking + p50_on_deck_slots = ["B3", "C3"] + p50_extension_tips = [tip50_3, tip50_4] + p50_manual_tips = [tip50_5, tip50_6, tip50_7, tip50_8, tip50_9, tip50_10] + p50_slots = ["B3", "C3", "A4", "B4"] + p50_extension_slots = ["A4", "B4"] + + p200_on_deck_slots = ["B2", "C2", "D2"] + p200_extension_tips = [tip200_4, tip200_5] + p200_manual_tips = [tip200_6, tip200_7, tip200_8, tip200_9] + p200_slots = ["B2", "C2", "D2", "C4", "D4"] + p200_extension_slots = ["C4", "D4"] + + if Columns == 5: + p50 = ctx.load_instrument( + "flex_8channel_50", + "right", + tip_racks=[tip50_1, tip50_2, tip50_3, tip50_4, tip50_5, tip50_6, tip50_7, tip50_8, tip50_9, tip50_10, tip50_11, tip50_12], + ) + p200 = ctx.load_instrument( + "flex_8channel_1000", + "left", + tip_racks=[tip200_1, tip200_2, tip200_3, tip200_4, tip200_5, tip200_6, tip200_7, tip200_8, tip200_9, tip200_10, tip200_11], + ) + + # Lists for Tip Tracking + p50_on_deck_slots = ["B3", "C3"] + p50_extension_tips = [tip50_3, tip50_4] + p50_manual_tips = [tip50_5, tip50_6, tip50_7, tip50_8, tip50_9, tip50_10, tip50_11, tip50_12] + p50_slots = ["B3", "C3", "A4", "B4"] + p50_extension_slots = ["A4", "B4"] + + p200_on_deck_slots = ["B2", "C2", "D2"] + p200_extension_tips = [tip200_4, tip200_5] + p200_manual_tips = [tip200_6, tip200_7, tip200_8, tip200_9, tip200_10, tip200_11] + p200_slots = ["B2", "C2", "D2", "C4", "D4"] + p200_extension_slots = ["C4", "D4"] + + if Columns == 6: + p50 = ctx.load_instrument( + "flex_8channel_50", + "right", + tip_racks=[ + tip50_1, + tip50_2, + tip50_3, + tip50_4, + tip50_5, + tip50_6, + tip50_7, + tip50_8, + tip50_9, + tip50_10, + tip50_11, + tip50_12, + tip50_13, + tip50_14, + ], + ) + p200 = ctx.load_instrument( + "flex_8channel_1000", + "left", + tip_racks=[ + tip200_1, + tip200_2, + tip200_3, + tip200_4, + tip200_5, + tip200_6, + tip200_7, + tip200_8, + tip200_9, + tip200_10, + tip200_11, + tip200_12, + tip200_13, + ], + ) + + # Lists for Tip Tracking + p50_on_deck_slots = ["B3", "C3"] + p50_extension_tips = [tip50_3, tip50_4] + p50_manual_tips = [tip50_5, tip50_6, tip50_7, tip50_8, tip50_9, tip50_10, tip50_11, tip50_12, tip50_13, tip50_14] + p50_slots = ["B3", "C3", "A4", "B4"] + p50_extension_slots = ["A4", "B4"] + + p200_on_deck_slots = ["B2", "C2", "D2"] + p200_extension_tips = [tip200_4, tip200_5] + p200_manual_tips = [tip200_6, tip200_7, tip200_8, tip200_9, tip200_10, tip200_11, tip200_12, tip200_13] + p200_slots = ["B2", "C2", "D2", "C4", "D4"] + p200_extension_slots = ["C4", "D4"] + p50.flow_rate.aspirate = 10 + p50.flow_rate.blow_out = 3 + p200.flow_rate.blow_out = 40 + p50.default_speed = 500 + p200.default_speed = 500 + + # Reagent Assignments + # REAGENT PLATE + HPMM = reagent_plate["A1"] + RDMM = reagent_plate["A2"] + PRMM = reagent_plate["A3"] + EB = reagent_plate["A4"] + EPH3 = reagent_plate["A5"] + FSMM = reagent_plate["A6"] + SMM = reagent_plate["A7"] + ATL4 = reagent_plate["A8"] + LIGX = reagent_plate["A9"] + STL = reagent_plate["A10"] + EPM = reagent_plate["A11"] + + # RESERVIOR + RNA_BEADS = Reservior["A1"] + AMP = Reservior["A2"] + RSB = Reservior["A3"] + ETOH = Reservior.rows()[0][3:12] + + # Plate Assignments + sample_1_as = sample_1.rows()[0][:12] + sample_2_as = sample_2.rows()[0][:12] + Anchors = Index_Anchors.rows()[0][Index_Anchor_start - 1 : 12] + Index_Adap = Index_Plate.rows()[0][Index_Adapter_start - 1 : 12] + + def move_gripper(labware, new_location): # shortened version of move_labware function + ctx.move_labware( + labware, + new_location, + use_gripper=True, + ) + + def move_chute(labware): # move labware to trash chute + ctx.move_labware(labware, chute, use_gripper=True) if DryRun == False else ctx.move_labware(labware, chute, use_gripper=False) + + def move_offdeck(labware, new_location): # manually move labware from off deck locations onto deck + ctx.move_labware( + labware, + new_location, + use_gripper=False, + ) + + def drop_tip(pipette): + if DryRun == True: + pipette.return_tip() + else: + pipette.drop_tip() + + def mix(mix, volume, reagent): + p50.flow_rate.aspirate = 12 + p50.configure_for_volume(volume) + for x in range(mix): + p50.aspirate(volume, reagent.bottom(0.4)) + p50.dispense(volume, reagent.bottom(1), push_out=0) + ctx.delay(seconds=5) + p50.blow_out(reagent.top(-11)) + p50.flow_rate.aspirate = 10 + + def cleanup_mix(mix, volume, reagent): + for x in range(mix): + p200.aspirate(volume, reagent.bottom(0.4)) + p200.dispense(volume, reagent.top(-10), push_out=0) + ctx.delay(seconds=5) + p200.blow_out(reagent.top(-7)) + + def get_next_tip(pipette): + if pipette == p50: + try: + p50.pick_up_tip() + except: + for x in p50.tip_racks[:]: + if x.wells()[-1].has_tip == False: # if tip racks are empty move to chute and remove empty tip rack from list + move_chute(x) + p50.tip_racks.remove(x) + try: # move tip racks on extension slots to deck + for y, z in zip(p50_extension_tips, p50_on_deck_slots): + ctx.move_labware(y, z, use_gripper=True) + p50.pick_up_tip() + for b in range(len(p50_on_deck_slots)): + del p50_extension_tips[0] + except: + # assumes all relevant deck slots are empty + for x, a in zip(p50_manual_tips, p50_slots): + ctx.move_labware(x, a, use_gripper=False) + p50.pick_up_tip() + for b in range(len(p50_slots)): + try: + del p50_manual_tips[0] + except: + pass + for c in p50_extension_slots: # only add tipracks on extension slot to extension tip list + try: + p50_extension_tips.append(ctx.deck[c]) + except: + pass + if pipette == p200: + try: + p200.pick_up_tip() + except: + for x in p200.tip_racks[:]: + if x.wells()[-1].has_tip == False: # if tip racks are empty move to chute and remove empty tip rack from list + move_chute(x) + p200.tip_racks.remove(x) + try: # move tip racks on extension slots to deck + for y, z in zip(p200_extension_tips, p200_on_deck_slots): + ctx.move_labware(y, z, use_gripper=True) + p200.pick_up_tip() + for b in range(2): + try: + del p200_extension_tips[0] + except: + pass + except: + # assumes all relevant deck slots are empty + for x, a in zip(p200_manual_tips, p200_slots): + ctx.move_labware(x, a, use_gripper=False) + p200.pick_up_tip() + for b in range(len(p200_slots)): + try: + del p200_manual_tips[0] + except: + pass + for c in p200_extension_slots: # only add tipracks on extension slot to extension tip list + p200_extension_tips.append(ctx.deck[c]) + + ctx.comment(f"{p200.default_speed}") + # Commands + # HYBRIDIZE PROBES + ################################################################################################ + temp_block.set_temperature(4) + if DryRun == False: + if on_deck_thermo == True: + thermo.set_lid_temperature(100) + thermo.set_block_temperature(95) + ctx.comment("---------HYBRIDIZE PROBES---------") + get_next_tip(p50) + if Columns > 1: + mix(8, 3 * Columns, HPMM) + p50.aspirate(4, HPMM.bottom(z=0.4)) + p50.dispense(4, sample_1_as[0], push_out=0) + p50.blow_out(sample_1_as[0].top(-10)) + mix(10, 10, sample_1_as[0]) + drop_tip(p50) + for x in range(Columns - 1): # Looping through remaing columns with new tips + get_next_tip(p50) + p50.aspirate(4, HPMM.bottom(z=0.5)) + p50.dispense(4, sample_1_as[x + 1].bottom(z=0.5), push_out=0) + p50.blow_out(sample_1_as[x + 1].top(-10)) + mix(10, 10, sample_1_as[x + 1]) + drop_tip(p50) + if on_deck_thermo == True: + thermo.open_lid() + move_gripper(sample_1, thermo) + thermo.close_lid() + if DryRun == False: + profile_HYB_DP1 = [{"temperature": 95, "hold_time_minutes": 2}] + thermo.execute_profile(steps=profile_HYB_DP1, repetitions=1, block_max_volume=15) + start_temp = 95 + temp = start_temp + while temp > 37: # Setting .1C/s ramp rate + thermo.set_block_temperature(temp) + temp -= 2.4 + thermo.set_block_temperature(37) + thermo.open_lid() + thermo.set_block_temperature(20) + else: + ctx.pause("Transfer plate to thermocycler and begin HYB-DP1 program") + move_offdeck(sample_1, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot C1") + move_offdeck(sample_1, "C1") + + # DEPLETE rRNA + #################################################################################################### + ctx.comment("---------DEPLETE rRNA---------") + get_next_tip(p50) + if Columns > 1: + mix(8, 3 * Columns, RDMM) + p50.aspirate(5, RDMM.bottom(z=0.5)) + p50.dispense(5, sample_1_as[0], push_out=0) + p50.blow_out(sample_1_as[0].top(-10)) + mix(10, 15, sample_1_as[0]) + drop_tip(p50) + for x in range(Columns - 1): # Looping through remaing coluns with new tips + get_next_tip(p50) + p50.aspirate(5, RDMM) + p50.dispense(5, sample_1_as[x + 1], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_1_as[x + 1].top(-10)) + mix(10, 15, sample_1_as[x + 1]) + drop_tip(p50) + if on_deck_thermo == True: + thermo.close_lid() + if DryRun == False: + profile_RNA_DEP = [{"temperature": 37, "hold_time_minutes": 15}] + thermo.execute_profile(steps=profile_RNA_DEP, repetitions=1, block_max_volume=20) + thermo.set_block_temperature(4) + thermo.open_lid() + else: + ctx.pause("Transfer plate to thermocycler and begin RNA_DEP program") + move_offdeck(sample_1, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot C1") + move_offdeck(sample_1, "C1") + + # REMOVE PROBES + ##################################################################################################### + ctx.comment("---------REMOVE PROBES---------") + get_next_tip(p50) + mix(10, 8 * Columns, PRMM) + p50.aspirate(10, PRMM.bottom(z=0.5)) + p50.dispense(10, sample_1_as[0], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_1_as[0].top(-10)) + mix(8, 20, sample_1_as[0]) + drop_tip(p50) + for x in range(Columns - 1): # Looping through remaing coluns with new tips + get_next_tip(p50) + p50.aspirate(10, PRMM.bottom(z=0.5)) + p50.dispense(10, sample_1_as[x + 1], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_1_as[x + 1].top(-10)) + mix(10, 20, sample_1_as[x + 1]) + drop_tip(p50) + if on_deck_thermo == True: + thermo.close_lid() + if DryRun == False: + profile_PRB_REM = [{"temperature": 37, "hold_time_minutes": 15}, {"temperature": 70, "hold_time_minutes": 15}] + thermo.execute_profile(steps=profile_PRB_REM, repetitions=1, block_max_volume=30) + thermo.set_block_temperature(4) + thermo.open_lid() + move_gripper(sample_1, "C1") + else: + ctx.pause("Transfer plate to thermocycler and begin PRB_REM program") + move_offdeck(sample_1, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot C1") + move_offdeck(sample_1, "C1") + + # CLEANUP RNA + ###################################################################################################### + ctx.comment("---------CLEANUP RNA---------") + p200.flow_rate.aspirate = 2000 + p200.flow_rate.dispense = 2000 + get_next_tip(p200) + if Columns <= 4: + p200.mix(20, 50 * Columns, RNA_BEADS) + else: + p200.mix(20, 200, RNA_BEADS) + p200.flow_rate.aspirate = 160 + p200.flow_rate.dispense = 100 + ctx.delay(seconds=3) + p200.blow_out() + drop_tip(p200) + for x in range(Columns): # adding RNA Cleanup beads to wells + get_next_tip(p200) + p200.aspirate(60, RNA_BEADS.bottom(z=0.5)) + p200.dispense(60, sample_1_as[x].top(-10)) + cleanup_mix(15, 80, sample_1_as[x]) + drop_tip(p200) + ctx.delay(minutes=5) + move_gripper(sample_1, mag_block) + ctx.delay(minutes=5) + for x in range(Columns): # removing supernatant + get_next_tip(p200) + p200.aspirate(80, sample_1_as[x]) + p200.dispense(80, chute) + drop_tip(p200) + for z in range(2): # 2 EtOH washes + for x in range(Columns): # adding EtOH + get_next_tip(p200) + p200.aspirate(175, ETOH[x]) + p200.dispense(175, sample_1_as[x].top(z=-3)) + drop_tip(p200) + if Columns <= 2: + ctx.delay(seconds=20) + for x in range(Columns): # remove EtOH + get_next_tip(p200) + p200.aspirate(80, sample_1_as[x].bottom(z=6)) + p200.aspirate(95, sample_1_as[x]) + p200.dispense(175, chute) + drop_tip(p200) + for x in range(Columns): # remove excess liquid + get_next_tip(p50) + p50.aspirate(50, sample_1_as[x].bottom(z=0.5)) + p50.dispense(50, chute) + drop_tip(p50) + if Columns <= 2: + ctx.delay(minutes=1.2) # air dry beads + move_gripper(sample_1, "C1") + for x in range(Columns): # resuspend beads + get_next_tip(p50) + p50.aspirate(10.5, EB.bottom(z=0.5)) + p50.dispense(10.5, sample_1_as[x], push_out=0) + ctx.delay(seconds=5) + p50.blow_out(sample_1_as[x].top(-10)) + mix(20, 8, sample_1_as[x]) + drop_tip(p50) + ctx.delay(minutes=2) + move_gripper(sample_1, mag_block) + ctx.delay(minutes=2) + for x in range(Columns): # transfer + get_next_tip(p50) + p50.aspirate(8.5, sample_1_as[x].bottom(z=0.5)) + if Columns > 3: + p50.dispense(8.5, sample_1_as[x + 6], push_out=0) + ctx.delay(seconds=5) + p50.blow_out(sample_1_as[x + 6].top(-10)) + else: + p50.dispense(8.5, sample_1_as[x + 4], push_out=0) + ctx.delay(seconds=5) + p50.blow_out(sample_1_as[x + 4].top(-10)) + drop_tip(p50) + + # FRAGMENT AND DENATURE RNA + ########################################################################################################## + ctx.comment("--------Fragment and Denature RNA---------") + if on_deck_thermo == True: + if DryRun == False: + thermo.set_lid_temperature(100) + thermo.set_block_temperature(94) + thermo.open_lid() + move_gripper(sample_1, "C1") + for x in range(Columns): + get_next_tip(p50) + p50.aspirate(8.5, EPH3.bottom(z=0.5)) + if Columns > 3: + p50.dispense(8.5, sample_1_as[x + 6], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_1_as[x + 6].top(-10)) + mix(10, 13, sample_1_as[x + 6]) + else: + p50.dispense(8.5, sample_1_as[x + 4], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_1_as[x + 4].top(-10)) + mix(10, 13, sample_1_as[x + 4]) + drop_tip(p50) + if on_deck_thermo == True: + move_gripper(sample_1, thermo) + thermo.close_lid() + if DryRun == False: + profile_DEN_RNA = [{"temperature": 94, "hold_time_minutes": 2}] + thermo.execute_profile(steps=profile_DEN_RNA, repetitions=1, block_max_volume=17) + thermo.set_block_temperature(4) + thermo.open_lid() + else: + ctx.pause("Transfer plate to thermocycler and begin DEN_RNA program") + move_offdeck(sample_1, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot C1") + move_offdeck(sample_1, "C1") + + # FIRST STRAND SYNTHESIS + ############################################################################################################## + ctx.comment("---------First Strand Synthesis---------") + get_next_tip(p50) + mix(10, 8 * Columns, FSMM) + p50.aspirate(8, FSMM.bottom(z=0.5)) + if Columns > 3: + p50.dispense(8, sample_1_as[6].top(-10)) + mix(10, 20, sample_1_as[6]) + else: + p50.dispense(8, sample_1_as[4].top(-10)) + mix(10, 20, sample_1_as[4]) + drop_tip(p50) + for x in range(Columns - 1): # looping through remaining columns with new tips + get_next_tip(p50) + p50.aspirate(8, FSMM.bottom(z=0.5)) + if Columns > 3: + p50.dispense(8, sample_1_as[x + 7].top(-10)) + mix(10, 20, sample_1_as[x + 7]) + else: + p50.dispense(8, sample_1_as[x + 5].top(-10)) + mix(10, 20, sample_1_as[x + 5]) + drop_tip(p50) + if on_deck_thermo == True: + thermo.close_lid() + if DryRun == False: + profile_FSS = [ + {"temperature": 25, "hold_time_minutes": 10}, + {"temperature": 42, "hold_time_minutes": 15}, + {"temperature": 70, "hold_time_minutes": 15}, + ] + thermo.execute_profile(steps=profile_FSS, repetitions=1, block_max_volume=25) + thermo.set_block_temperature(4) + thermo.open_lid() + else: + ctx.pause("Transfer plate to thermocycler and begin FSS program") + move_offdeck(sample_1, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot C1") + move_offdeck(sample_1, "C1") + + # SECOND STRAND SYNTHESIS + ################################################################################################################ + ctx.comment("---------Second Strand Synthesis---------") + if on_deck_thermo == True: + if DryRun == False: + thermo.set_lid_temperature(40) + for x in range(Columns): + get_next_tip(p50) + p50.aspirate(25, SMM.bottom(z=0.5)) + if Columns > 3: + p50.dispense(25, sample_1_as[x + 6].top(-10)) + mix(10, 40, sample_1_as[x + 6]) + else: + p50.dispense(25, sample_1_as[x + 4].top(-10)) + mix(10, 40, sample_1_as[x + 4]) + drop_tip(p50) + if on_deck_thermo == True: + thermo.close_lid() + if DryRun == False: + profile_SSS = [{"temperature": 16, "hold_time_minutes": 60}] + thermo.execute_profile(steps=profile_SSS, repetitions=1, block_max_volume=50) + thermo.set_block_temperature(4) + thermo.open_lid() + move_gripper(sample_1, "C1") + else: + ctx.pause("Transfer plate to thermocycler and begin SSS program") + move_offdeck(sample_1, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot C1") + move_offdeck(sample_1, "C1") + + # CLEANUP cDNA + ################################################################################################################## + ctx.comment("---------CLEANUP cDNA---------") + p200.flow_rate.aspirate = 2000 + p200.flow_rate.dispense = 2000 + get_next_tip(p200) + if Columns <= 1: + p200.mix(50, 160 * Columns, AMP) + else: + p200.mix(50, 200, AMP) + ctx.delay(seconds=3) + p200.flow_rate.aspirate = 160 + p200.flow_rate.dispense = 100 + p200.blow_out(AMP.top(z=-5)) + drop_tip(p200) + for x in range(Columns): # adding Ampure beads + get_next_tip(p200) + p200.aspirate(90, AMP) + if Columns > 3: + p200.dispense(90, sample_1_as[x + 6].top(-10)) + cleanup_mix(10, 120, sample_1_as[x + 6]) + else: + p200.dispense(90, sample_1_as[x + 4].top(-10)) + cleanup_mix(10, 120, sample_1_as[x + 4]) + drop_tip(p200) + ctx.delay(minutes=5) + move_gripper(sample_1, mag_block) + ctx.delay(minutes=5) + for x in range(Columns): # remove supernatant + get_next_tip(p200) + if Columns > 3: + p200.aspirate(70, sample_1_as[x + 6].bottom(4)) + p200.aspirate(65, sample_1_as[x + 6].bottom(z=0.5)) + else: + p200.aspirate(70, sample_1_as[x + 4].bottom(4)) + p200.aspirate(65, sample_1_as[x + 4].bottom(z=0.5)) + p200.dispense(130, chute) + drop_tip(p200) + for z in range(2): + for x in range(Columns): # add EtOH + get_next_tip(p200) + p200.aspirate(175, ETOH[x]) + if Columns > 3: + p200.dispense(175, sample_1_as[x + 6].top(z=-3)) + else: + p200.dispense(175, sample_1_as[x + 4].top(z=-3)) + drop_tip(p200) + if Columns == 1: + ctx.delay(seconds=20) + for x in range(Columns): # remove EtOH + get_next_tip(p200) + if Columns > 3: + p200.aspirate(80, sample_1_as[x + 6].bottom(z=6)) + p200.aspirate(95, sample_1_as[x + 6].bottom(0.3)) + else: + p200.aspirate(80, sample_1_as[x + 4].bottom(z=6)) + p200.aspirate(95, sample_1_as[x + 4].bottom(0.3)) + p200.dispense(175, chute) + drop_tip(p200) + for x in range(Columns): # Remove residual liquid + get_next_tip(p50) + if Columns > 3: + p50.aspirate(50, sample_1_as[x + 6].bottom(z=0.5)) + else: + p50.aspirate(50, sample_1_as[x + 4].bottom(z=0.5)) + p50.dispense(50, chute) + drop_tip(p50) + if Columns <= 2: + ctx.delay(minutes=1.2) # dry beads + move_gripper(sample_1, "C1") + for x in range(Columns): # resuspend beads + get_next_tip(p50) + p50.aspirate(19.5, RSB.bottom(z=0.5)) + if Columns > 3: + p50.dispense(19.5, sample_1_as[x + 6], push_out=0) + ctx.delay(seconds=2) + p50.blow_out(sample_1_as[x + 6].top(-10)) + mix(25, 16, sample_1_as[x + 6]) + else: + p50.dispense(19.5, sample_1_as[x + 4], push_out=0) + ctx.delay(seconds=2) + p50.blow_out(sample_1_as[x + 4].top(-10)) + mix(25, 16, sample_1_as[x + 4]) + drop_tip(p50) + ctx.delay(minutes=2) + move_gripper(sample_1, mag_block) + if Columns > 3: + move_offdeck(sample_2, "C1") + ctx.delay(minutes=2) + for x in range(Columns): # transfer + get_next_tip(p50) + if Columns > 3: + p50.aspirate(17.5, sample_1_as[x + 6].bottom(z=0.5)) + p50.dispense(17.5, sample_2_as[x], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_2_as[x].top(-10)) + else: + p50.aspirate(17.5, sample_1_as[x + 4].bottom(z=0.5)) + p50.dispense(17.5, sample_1_as[x + 8], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_1_as[x + 8].top(-10)) + drop_tip(p50) + if Columns > 3: + move_chute(sample_1) + + # SAFE STOPPING POINT + # dA-TAILING + ################################################################################################################# + ctx.comment("---------Adenylate 3-Prime Ends---------") + if Columns <= 3: + if on_deck_thermo == True: + move_gripper(sample_1, thermo) + else: + move_gripper(sample_1, "C1") + if on_deck_thermo == True: + if DryRun == False: + thermo.set_lid_temperature(100) + thermo.set_block_temperature(37) + thermo.open_lid() + for x in range(Columns): + get_next_tip(p50) + p50.aspirate(12.5, ATL4.bottom(z=0.5)) + if Columns > 3: + p50.dispense(12.5, sample_2_as[x].top(-10)) + mix(10, 25, sample_2_as[x]) + else: + p50.dispense(12.5, sample_1_as[x + 8].top(-10)) + mix(10, 25, sample_1_as[x + 8]) + drop_tip(p50) + if on_deck_thermo == True: + if Columns > 3: + move_gripper(sample_2, thermo) + else: + move_gripper(sample_1, thermo) + thermo.close_lid() + if DryRun == False: + profile_ATAIL = [{"temperature": 37, "hold_time_minutes": 30}, {"temperature": 70, "hold_time_minutes": 5}] + thermo.execute_profile(steps=profile_ATAIL, repetitions=1, block_max_volume=30) + thermo.set_block_temperature(4) + thermo.open_lid() + else: + ctx.pause("Transfer plate to thermocycler and begin ATAIL program") + if Columns > 3: + move_offdeck(sample_2, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot B1") + move_offdeck(sample_2, "B1") + else: + move_offdeck(sample_1, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot B1") + move_offdeck(sample_1, "B1") + move_offdeck(Index_Anchors, "C1") + + # LIGATE ANCHORS + ################################################################################################################# + ctx.comment("---------LIGATE ANCHORS---------") + p50.configure_for_volume(2.5) + for x in range(Columns): + get_next_tip(p50) + p50.aspirate(2.5, RSB.bottom(z=0.5)) + if Columns > 3: + p50.dispense(2.5, sample_2_as[x].top(-10)) + else: + p50.dispense(2.5, sample_1_as[x + 8].top(-10)) + drop_tip(p50) + for x in range(Columns): + get_next_tip(p50) + p50.move_to(Anchors[x].top(-10)) + p50.touch_tip(Anchors[x], radius=1.2, v_offset=-10, speed=20) + drop_tip(p50) + get_next_tip(p50) + p50.aspirate(2.5, Anchors[x].bottom(z=0.5)) + if Columns > 3: + p50.dispense(2.5, sample_2_as[x].top(-10)) + else: + p50.dispense(2.5, sample_1_as[x + 8].top(-10)) + drop_tip(p50) + for x in range(Columns): # break surface tension here + p50.flow_rate.aspirate = 2 + get_next_tip(p50) + p50.move_to(LIGX.bottom(0.3)) + ctx.delay(seconds=3) + p50.aspirate(2.5, LIGX.bottom(z=0.3)) + p50.flow_rate.aspirate = 8 + if Columns > 3: + p50.dispense(2.5, sample_2_as[x].top(-10)) + mix(12, 30, sample_2_as[x]) + else: + p50.dispense(2.5, sample_1_as[x + 8].top(-10)) + mix(12, 30, sample_1_as[x + 8]) + drop_tip(p50) + move_offdeck(Index_Anchors, protocol_api.OFF_DECK) + if on_deck_thermo == True: + thermo.close_lid() + if DryRun == False: + profile_LIG = [{"temperature": 30, "hold_time_minutes": 10}] + thermo.execute_profile(steps=profile_LIG, repetitions=1, block_max_volume=38) + thermo.set_block_temperature(4) + thermo.open_lid() + else: + ctx.pause("Transfer plate to thermocycler and begin LIG program") + if Columns > 3: + move_offdeck(sample_2, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot B1") + move_offdeck(sample_2, "B1") + else: + move_offdeck(sample_1, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot B1") + move_offdeck(sample_1, "B1") + + # STOP LIGATION + ############################################################################################################ + ctx.comment("---------STOP LIGATION---------") + p50.configure_for_volume(5) + for x in range(Columns): + get_next_tip(p50) + p50.aspirate(5, STL.bottom(z=0.4)) + if Columns > 3: + p50.dispense(5, sample_2_as[x], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_2_as[x].top(-10)) + else: + p50.dispense(5, sample_1_as[x + 8], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_1_as[x + 8].top(-10)) + drop_tip(p50) + for x in range(Columns): + get_next_tip(p50) + if Columns > 3: + mix(15, 35, sample_2_as[x]) + else: + mix(15, 35, sample_1_as[x + 8]) + drop_tip(p50) + + # CLEANUP FRAGMENTS + ############################################################################################################# + ctx.comment("---------CLEANUP FRAGMENTS---------") + if Columns > 3: + move_gripper(sample_2, "C1") + else: + move_gripper(sample_1, "C1") + get_next_tip(p200) + p200.flow_rate.aspirate = 2000 + p200.flow_rate.dispense = 2000 + if Columns <= 2: + p200.mix(20, 80 * Columns, AMP) + else: + p200.mix(20, 200, AMP) + ctx.delay(seconds=3) + p200.flow_rate.aspirate = 160 + p200.flow_rate.dispense = 100 + p200.blow_out() + drop_tip(p200) + for x in range(Columns): # adding Ampure Beads + get_next_tip(p200) + p200.aspirate(34, AMP.bottom(z=0.5)) + if Columns > 3: + p200.dispense(34, sample_2_as[x].top(-10)) + cleanup_mix(15, 75, sample_2_as[x]) + else: + p200.dispense(34, sample_1_as[x + 8].top(-10)) + cleanup_mix(15, 75, sample_1_as[x + 8]) + drop_tip(p200) + ctx.delay(minutes=5) + if Columns > 3: + move_gripper(sample_2, mag_block) + else: + move_gripper(sample_1, mag_block) + ctx.delay(minutes=5) + for x in range(Columns): # remove supernatant + get_next_tip(p200) + if Columns > 3: + p200.aspirate(75, sample_2_as[x]) + else: + p200.aspirate(75, sample_1_as[x + 8]) + p200.dispense(75, chute) + drop_tip(p200) + for z in range(2): + for x in range(Columns): # adding EtOH + get_next_tip(p200) + p200.aspirate(175, ETOH[x]) + if Columns > 3: + p200.dispense(175, sample_2_as[x].top(z=-3)) + else: + p200.dispense(175, sample_1_as[x + 8].top(z=-3)) + drop_tip(p200) + if Columns == 1: + ctx.delay(seconds=20) + for x in range(Columns): # remove EtOH + get_next_tip(p200) + if Columns > 3: + p200.aspirate(80, sample_2_as[x].bottom(z=6)) + p200.aspirate(95, sample_2_as[x].bottom(z=0.3)) + else: + p200.aspirate(80, sample_1_as[x + 8].bottom(z=6)) + p200.aspirate(95, sample_1_as[x + 8].bottom(z=0.3)) + p200.dispense(175, chute) + drop_tip(p200) + for x in range(Columns): # remove residual liquid + get_next_tip(p50) + if Columns > 3: + p50.aspirate(50, sample_2_as[x].bottom(z=0.3)) + else: + p50.aspirate(50, sample_1_as[x + 8].bottom(z=0.3)) + p50.dispense(50, chute) + drop_tip(p50) + if Columns <= 2: + ctx.delay(minutes=1.2) # dry beads + if Columns > 3: + move_gripper(sample_2, "C1") + else: + move_gripper(sample_1, "C1") + for x in range(Columns): # resuspend beads + get_next_tip(p50) + p50.aspirate(22, RSB.bottom(z=0.5)) + if Columns > 3: + p50.dispense(22, sample_2_as[x], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_2_as[x].top(-10)) + mix(20, 15, sample_2_as[x]) + else: + p50.dispense(22, sample_1_as[x + 8], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_1_as[x + 8].top(-10)) + mix(20, 15, sample_1_as[x + 8]) + drop_tip(p50) + ctx.delay(minutes=2) + if Columns > 3: + move_gripper(sample_2, mag_block) + else: + move_gripper(sample_1, mag_block) + if Columns <= 3: + if on_deck_thermo == True: + move_offdeck(sample_2, "C1") + else: + move_offdeck(sample_2, "B1") + else: + if on_deck_thermo == True: + move_offdeck(sample_3, "C1") + else: + move_offdeck(sample_3, "B1") + ctx.delay(minutes=1.5) + if on_deck_thermo == True: + thermo.open_lid() + for x in range(Columns): # transfer + get_next_tip(p50) + if Columns > 3: + p50.aspirate(20, sample_2_as[x].bottom(z=0.5)) + p50.dispense(20, sample_3_as[x], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_3_as[x].top(-10)) + else: + p50.aspirate(20, sample_1_as[x + 8].bottom(z=0.5)) + p50.dispense(20, sample_2_as[x], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_2_as[x].top(-10)) + drop_tip(p50) + + if on_deck_thermo == True: + if Columns > 3: + move_gripper(sample_3, thermo) + else: + move_gripper(sample_2, thermo) + if Columns > 3: + move_chute(sample_2) + else: + move_chute(sample_1) + + # AMPLIFY LIBRARIES + ################################################################################################### + ctx.comment("---------AMPLIFY LIBRARIES---------") + move_offdeck(Index_Plate, "C1") + for x in range(Columns): # poke holes in Index Plate Seal + get_next_tip(p50) + p50.move_to(Index_Adap[x].top(-10)) + p50.touch_tip(Index_Adap[x], radius=1.2, v_offset=-10, speed=20) + drop_tip(p50) + get_next_tip(p50) + mix(5, 7, Index_Adap[x]) + p50.aspirate(10, Index_Adap[x].bottom(z=0.5)) + if Columns > 3: + p50.dispense(10, sample_3_as[x].top(-10)) + else: + p50.dispense(10, sample_2_as[x].top(-10)) + drop_tip(p50) + for x in range(Columns): + get_next_tip(p50) + p50.aspirate(20, EPM.bottom(z=0.5)) + if Columns > 3: + p50.dispense(20, sample_3_as[x].top(-10)) + mix(10, 45, sample_3_as[x]) + else: + p50.dispense(20, sample_2_as[x].top(-10)) + mix(10, 45, sample_2_as[x]) + drop_tip(p50) + move_offdeck(Index_Plate, protocol_api.OFF_DECK) + if on_deck_thermo == True: + thermo.close_lid() + if DryRun == False: + profile_PCR_1 = [{"temperature": 98, "hold_time_minutes": 30}] + thermo.execute_profile(steps=profile_PCR_1, repetitions=1, block_max_volume=50) + profile_PCR_2 = [ + {"temperature": 98, "hold_time_seconds": 10}, + {"temperature": 60, "hold_time_seconds": 30}, + {"temperature": 72, "hold_time_seconds": 30}, + ] + thermo.execute_profile(steps=profile_PCR_2, repetitions=PCR_Cycles, block_max_volume=50) + profile_PCR_3 = [{"temperature": 72, "hold_time_minutes": 5}] + thermo.execute_profile(steps=profile_PCR_3, repetitions=1, block_max_volume=50) + thermo.set_block_temperature(4) + thermo.open_lid() + else: + if Columns > 3: + ctx.pause("Transfer plate to thermocycler and begin PCR program") + move_offdeck(sample_3, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot B1") + move_offdeck(sample_3, "B1") + else: + ctx.pause("Transfer plate to thermocycler and begin PCR program") + move_offdeck(sample_2, protocol_api.OFF_DECK) + ctx.pause("Quick spin plate, remove seal and place on slot B1") + move_offdeck(sample_2, "B1") + + # CLEANUP LIBRARIES + ######################################################################################################## + ctx.comment("---------CLEANUP LIBRARIES---------") + p200.flow_rate.aspirate = 2000 + p200.flow_rate.dispense = 2000 + if Columns > 3: + move_gripper(sample_3, "C1") + else: + move_gripper(sample_2, "C1") + if on_deck_thermo == True: + thermo.deactivate_lid() + thermo.deactivate_block() + get_next_tip(p200) + if Columns <= 5: + p200.mix(30, 40 * Columns, AMP) + else: + p200.mix(30, 200, AMP) + ctx.delay(seconds=3) + p200.blow_out(AMP.top(z=-7)) + drop_tip(p200) + p200.flow_rate.aspirate = 160 + p200.flow_rate.dispense = 100 + for x in range(Columns): # adding Ampure Beads + get_next_tip(p200) + p200.aspirate(50, AMP) + ctx.delay(seconds=3) + if Columns > 3: + p200.dispense(50, sample_3_as[x].top(-10)) + cleanup_mix(15, 100, sample_3_as[x]) + else: + p200.dispense(50, sample_2_as[x].top(-10)) + cleanup_mix(15, 100, sample_2_as[x]) + drop_tip(p200) + ctx.delay(minutes=5) + if Columns > 3: + move_gripper(sample_3, mag_block) + else: + move_gripper(sample_2, mag_block) + ctx.delay(minutes=5) + for x in range(Columns): # remove supernatant + get_next_tip(p200) + if Columns > 3: + p200.aspirate(90, sample_3_as[x]) + else: + p200.aspirate(90, sample_2_as[x]) + ctx.delay(seconds=3) + p200.dispense(90, chute) + drop_tip(p200) + for z in range(2): + for x in range(Columns): # adding EtOH + get_next_tip(p200) + p200.aspirate(175, ETOH[x]) + if Columns > 3: + p200.dispense(175, sample_3_as[x].top(-3)) + else: + p200.dispense(175, sample_2_as[x].top(-3)) + drop_tip(p200) + if Columns == 1: + ctx.delay(seconds=20) + for x in range(Columns): # removing EtOH + get_next_tip(p200) + if Columns > 3: + p200.aspirate(80, sample_3_as[x].bottom(z=6)) + p200.aspirate(95, sample_3_as[x]) + else: + p200.aspirate(80, sample_2_as[x].bottom(z=6)) + p200.aspirate(95, sample_2_as[x]) + p200.dispense(175, chute) + drop_tip(p200) + for x in range(Columns): # remove excess liquid + get_next_tip(p50) + if Columns > 3: + p50.aspirate(50, sample_3_as[x]) + else: + p50.aspirate(50, sample_2_as[x]) + p50.dispense(50, chute) + drop_tip(p50) + if Columns <= 2: + ctx.delay(minutes=1.2) # dry beads + if Columns > 3: + move_gripper(sample_3, "C1") + else: + move_gripper(sample_2, "C1") + for x in range(Columns): # resuspend beads + get_next_tip(p50) + p50.aspirate(17, RSB.bottom(z=0.4)) + if Columns > 3: + p50.dispense(17, sample_3_as[x], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_3_as[x].top(-10)) + mix(15, 15, sample_3_as[x]) + else: + p50.dispense(17, sample_2_as[x], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_2_as[x].top(-10)) + mix(15, 15, sample_2_as[x]) + drop_tip(p50) + ctx.delay(minutes=2) + if Columns > 3: + move_gripper(sample_3, mag_block) + else: + move_gripper(sample_2, mag_block) + ctx.delay(minutes=2) + for x in range(Columns): # transfer + get_next_tip(p50) + if Columns > 3: + p50.aspirate(15, sample_3_as[x].bottom(z=0.4)) + p50.dispense(15, sample_3_as[x + 6], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_3_as[x + 6].top(-10)) + else: + p50.aspirate(15, sample_2_as[x].bottom(z=0.4)) + p50.dispense(15, sample_2_as[x + 4], push_out=0) + ctx.delay(seconds=3) + p50.blow_out(sample_2_as[x + 4].top(-10)) + drop_tip(p50) + if Columns > 3: + move_gripper(sample_3, "C1") + else: + move_gripper(sample_2, "C1") + ctx.home() + + # Liquid Definitions and Assignments + ####################################################################################################################################################### + HPMM_ = ctx.define_liquid(name="Hybridization Probe Master Mix", description="Hybridization Probe Master Mix", display_color="#cc3399") + for well in reagent_plate.wells()[0:8]: + well.load_liquid(liquid=HPMM_, volume=4.8 * Columns) + RDMM_ = ctx.define_liquid(name="rRNA Depletion Master Mix", description="rRNA Depletion Master Mix", display_color="#ff6699") + for well in reagent_plate.wells()[8:16]: + well.load_liquid(liquid=RDMM_, volume=6 * Columns) + PRMM_ = ctx.define_liquid(name="Probe Removal Master Mix", description="Probe Removal Master Mix", display_color="#ffcc99") + for well in reagent_plate.wells()[16:24]: + well.load_liquid(liquid=PRMM_, volume=11 * Columns) + EB_ = ctx.define_liquid(name="Elution Buffer", description="Elution Buffer", display_color="#ff9966") + for well in reagent_plate.wells()[24:32]: + well.load_liquid(liquid=EB_, volume=12 * Columns) + EPH3_ = ctx.define_liquid(name="EPH3", description="Elute, Prime, Fragment 3HC Mix", display_color="#009933") + for well in reagent_plate.wells()[32:40]: + well.load_liquid(liquid=EPH3_, volume=10 * Columns) + FSMM_ = ctx.define_liquid(name="First Strand Master Mix", description="First Strand Master Mix", display_color="#0066ff") + for well in reagent_plate.wells()[40:48]: + well.load_liquid(liquid=FSMM_, volume=10 * Columns) + SMM_ = ctx.define_liquid(name="SMM", description="Second Strand Marking Master Mix", display_color="#00cc99") + for well in reagent_plate.wells()[48:56]: + well.load_liquid(liquid=SMM_, volume=27.5 * Columns) + ATL4_ = ctx.define_liquid(name="ATL4", description="A-Tailing Mix", display_color="#6699ff") + for well in reagent_plate.wells()[56:64]: + well.load_liquid(liquid=ATL4_, volume=13.8 * Columns) + LIGX_ = ctx.define_liquid(name="LIGX", description="Ligation Mix", display_color="#ff9933") + for well in reagent_plate.wells()[64:72]: + well.load_liquid(liquid=LIGX_, volume=3 * Columns) + STL_ = ctx.define_liquid(name="STL", description="Stop Ligation Buffer", display_color="#ffff99") + for well in reagent_plate.wells()[72:80]: + well.load_liquid(liquid=STL_, volume=6 * Columns) + EPM_ = ctx.define_liquid(name="EPM", description="Enhanced PCR Mix", display_color="#cc99ff") + for well in reagent_plate.wells()[80:88]: + well.load_liquid(liquid=EPM_, volume=22 * Columns) + RNA_BEADS_ = ctx.define_liquid(name="RNA XP Beads", description="RNAClean XP Beads", display_color="#66ffff") + for well in Reservior.wells()[0:8]: + well.load_liquid(liquid=RNA_BEADS_, volume=66 * Columns) + AMP_ = ctx.define_liquid(name="Ampure XP Beads", description="Ampure XP Beads", display_color="#663300") + for well in Reservior.wells()[8:16]: + well.load_liquid(liquid=AMP_, volume=200 * Columns) + RSB_ = ctx.define_liquid(name="Resuspension Buffer", description="Resuspension Buffer", display_color="#b3ffb3") + for well in Reservior.wells()[16:24]: + well.load_liquid(liquid=RSB_, volume=77 * Columns) + ETOH_ = ctx.define_liquid(name="80% Ethanol", description="80% Ethanol", display_color="#f2f2f2") + for well in Reservior.wells()[24 : 24 + (Columns * 8)]: + well.load_liquid(liquid=ETOH_, volume=1500) + Samples = ctx.define_liquid(name="Input RNA", description="Input RNA", display_color="#99ff99") + for well in sample_1.wells()[: Columns * 8]: + well.load_liquid(liquid=Samples, volume=11) diff --git a/app-testing/files/protocols/pl_KAPA_Library_Quant_48_v8.py b/app-testing/files/protocols/pl_KAPA_Library_Quant_48_v8.py new file mode 100644 index 00000000000..51f52da7fca --- /dev/null +++ b/app-testing/files/protocols/pl_KAPA_Library_Quant_48_v8.py @@ -0,0 +1,1116 @@ +from opentrons import protocol_api +from opentrons import types +import math + +metadata = { + "protocolName": "KAPA Library Quant 48x v8", + "author": "Opentrons ", + "source": "Protocol Library", +} +requirements = { + "robotType": "Flex", + "apiLevel": "2.18", +} + + +def add_parameters(parameters): + # ======================== RUNTIME PARAMETERS ======================== + parameters.add_bool(display_name="Dry Run", variable_name="DRYRUN", default=False, description="Whether to perform a dry run or not.") + parameters.add_int( + display_name="Sample Column count", + variable_name="COLUMNS", + default=3, + minimum=1, + maximum=6, + description="How many sample columns to process.", + ) + parameters.add_str( + display_name="qPCR Plate Format", + variable_name="FORMAT", + default="384", + description="qPCR Plate Format", + choices=[{"display_name": "384", "value": "384"}, {"display_name": "96", "value": "96"}], + ) + parameters.add_bool( + display_name="Tip Mixing", + variable_name="TIP_MIX", + default=False, + description="Whether or not to use Tip Mixing instead of a shaker", + ) + parameters.add_bool( + display_name="Thermocycler Module Present", + variable_name="ONDECK_THERMO", + default=True, + description="Is the Thermocycler Module present (but not used)", + ) + parameters.add_str( + display_name="Trash Position", + variable_name="TRASH_POSITION", + default="CHUTE", + description="Trash Position", + choices=[{"display_name": "Trash Chute in D3", "value": "CHUTE"}, {"display_name": "Trash Bin in A3", "value": "BIN"}], + ) + + +def run(protocol: protocol_api.ProtocolContext): + # ======================== DOWNLOADED PARAMETERS ======================== + global USE_GRIPPER # T/F Whether or not Using the Gripper + global STP_50_TIPS # T/F Whether or not there are p50 Single Tip Pickups + global STP_200_TIPS # T/F Whether or not there are p200 Single Tip Pickups + global REUSE_ANY_50_TIPS # T/F Whether or not Reusing any p50 + global REUSE_ANY_200_TIPS # T/F Whether or not Reusing any p200 + global TIP_TRASH # T/F whether or not the Tips are Returned + global COLUMNS # Number of Columns of Samples + global PLATE_STACKED # Number of Plates Stacked in Stacked Position + global p50_TIPS # Number of p50 tips currently available + global p200_TIPS # Number of p200 tips currently available + global p50_RACK_COUNT # Number of current total p50 racks + global p200_RACK_COUNT # Number of current total p200 racks + global tiprack_200_STP # Tiprack for p200 Single Tip Pickup + global tiprack_200_STR # Tiprack for p200 Single Tip Return + global tiprack_50_STP # Tiprack for p50 Single Tip Pickup + global tiprack_50_STR # Tiprack for p50 Single Tip Return + global tiprack_50_R # Tiprack for p50 Reuse + global tiprack_200_R1 # Tiprack for p200 Reuse #1 + global tiprack_200_R2 # Tiprack for p200 Reuse #2 + global WASTEVOL # Number - Total volume of Discarded Liquid Waste + global ETOHVOL # Number - Total volume of Available EtOH + # =================== LOADING THE RUNTIME PARAMETERS ==================== + + DRYRUN = protocol.params.DRYRUN + COLUMNS = protocol.params.COLUMNS + FORMAT = protocol.params.FORMAT + TIP_MIX = protocol.params.TIP_MIX + ONDECK_THERMO = protocol.params.ONDECK_THERMO + TRASH_POSITION = protocol.params.TRASH_POSITION + + # ================================================================================================= + # ====================================== ADVANCED PARAMETERS ====================================== + # ================================================================================================= + # -------PROTOCOL STEP------- + STEP_DILUTE = True # Set to 0 to skip block of commands + STEP_MIX = True # Set to 0 to skip block of commands + STEP_DISPENSE = True # Set to 0 to skip block of commands + # --------------------------- + # This notifies the user that for 5-6 columns (from more than 32 samples up to 48 samples) it requires Tip reusing in order to remain walkaway. + # This setting will override any Runtime parameter, and also pauses to notify the user. So if the user enters 6 columns with Single Tip Use, it will pause and warn that it has to change to Reusing tips in order to remain walkaway. + # Note that if omitting steps (i.e. skipping the last cleanup step) it is possible to do single use tips, but may vary on case by case basis. + # Note that it is also possible to use advanced settings to include pauses that requires user intervention to replenish tipracks, making allowing a run of single Use Tips. + AllSteps = [STEP_DILUTE, STEP_MIX, STEP_DISPENSE] + # if COLUMNS == 5 or COLUMNS == 6 and all(AllSteps) == True: + # protocol.pause('MUST REUSE TIPS') + # TIP_SETTING = 'Reusing Tips' + + INICOLUMN1 = "1" # Indicate the initial input columns, for example, Previous NGS library Prep output samples are in wells A10-A12 + INICOLUMN2 = "2" # Ignore input columns greater than intended number of columns, for example if doing 3 sample columns (plus 1 for Standards), ignore INICOLUMNS4 and up + INICOLUMN3 = "3" + INICOLUMN4 = "4" + INICOLUMN5 = "5" + INICOLUMN6 = "6" + DRYRUN = True # Whether or not to do a Dry Run, which is without heating or cooling an shortened incubation times. + TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack + DEACTIVATE_TEMP = True # Whether or not to deactivate the heating and cooling modules after a run + TRASH_POSITION = "BIN" # 'BIN' or 'CHUTE' + ONDECK_THERMO = True # On Deck Thermocycler + ONDECK_HEATERSHAKER = True # On Deck Heater Shaker + ONDECK_TEMP = True # On Deck Thermocycler + RES_TYPE_96x = False # Type of Reservoir, if reusing tips or omitting rows, set True to use a 96x2ml deepwell + ETOH_1_AirMultiDis = False # When adding WASH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + ETOH_2_AirMultiDis = False # When adding EtOH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + RSB_1_AirMultiDis = False # When adding RSB to multiple columns, dispense above the wells and reuse tips (Tip Saving) + CUSTOM_OFFSETS = True # Manually enter offset position settings + USE_GRIPPER = True # Using the Gripper + SWAPOFFDECK = False # Setting to Swap empty Tipracks to empty positions instead of dumping them + REUSE_50_TIPS_RSB = False # Reusing p50 tips + REUSE_200_TIPS_ETOH_1 = False # Reusing p200 tips + REUSE_200_TIPS_ETOH_2 = False # Reusing p200 tips + STP_200_TIPS = False # Single Tip Pickup p200 tips + STP_50_TIPS = False # Single tip Pickup p50 tips + REPORT = True # Whether or not to include Extra Comments for Debugging + LABEL = True # Whether or not to include Liquid Labeling + + # =========================== QUICK SETTINGS ============================ + if TRASH_POSITION == "BIN": + SWAPOFFDECK = True # Setting to Swap empty Tipracks to empty positions instead of dumping them + if TRASH_POSITION == "CHUTE": + SWAPOFFDECK = False # Setting to Swap empty Tipracks to empty positions instead of dumping them + if TIP_MIX == True: + ONDECK_HEATERSHAKER = True # On Deck Heater Shaker + if TIP_MIX == False: + ONDECK_HEATERSHAKER = False # On Deck Heater Shaker + if DRYRUN == True: + TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack + DEACTIVATE_TEMP = True # Whether or not to deactivate the heating and cooling modules after a run + REPORT = True # Whether or not to include Extra Comments for Debugging + + # ======================== BACKGROUND PARAMETERS ======================== + p50_TIPS = 0 # Number of p50 tips currently available + p200_TIPS = 0 # Number of p50 tips currently available + RESETCOUNT = 0 # Number of times the protocol was paused to reset tips + p50_RACK_COUNT = 0 # Number of current total p50 racks + p200_RACK_COUNT = 0 # Number of current total p200 racks + WASTEVOL = 0 # Number - Total volume of Discarded Liquid Waste + ETOHVOL = 0 # Number - Total volume of Available EtOH + PLATE_STACKED = 0 # Number of Plates Stacked in Stacked Position + REUSE_50_TIPS_COUNT = 0 + REUSE_ANY_50_TIPS = False + if REUSE_50_TIPS_RSB == True: + REUSE_ANY_50_TIPS = True + REUSE_50_TIPS_COUNT += COLUMNS + REUSE_200_TIPS_COUNT = 0 + REUSE_ANY_200_TIPS = False + if REUSE_200_TIPS_ETOH_1 == True: + REUSE_ANY_200_TIPS = True + REUSE_200_TIPS_COUNT += COLUMNS + if REUSE_200_TIPS_ETOH_2 == True: + REUSE_ANY_200_TIPS = True + REUSE_200_TIPS_COUNT += COLUMNS + + # =============================== PIPETTE =============================== + p1000 = protocol.load_instrument("flex_8channel_1000", "left") + p50 = protocol.load_instrument("flex_8channel_50", "right") + p1000_flow_rate_aspirate_default = 200 + p1000_flow_rate_dispense_default = 200 + p1000_flow_rate_blow_out_default = 400 + p50_flow_rate_aspirate_default = 50 + p50_flow_rate_dispense_default = 50 + p50_flow_rate_blow_out_default = 100 + + # ================================ LISTS ================================ + p50_RACKS_PIPET = [] # Pipette List + p200_RACKS_PIPET = [] # Pipette List + AVAILABLE_POS_ONDECK = [] # List of Available Positions ON DECK + AVAILABLE_POS_OFFDECK = [] # List of Available Positions OFF DECK + RACKS_TO_DUMP = [] # List of Emptied Racks ON DECK + p50_RACKS_ONDECK = [] # List of P50 Racks ON DECK + p50_RACKS_OFFDECK = [] # List of P50 Racks OFF DECK + p50_RACKS_DROPPED = [] # List of P50 Racks DROPPED + p200_RACKS_ONDECK = [] # List of P200 Racks ON DECK + p200_RACKS_OFFDECK = [] # List of P200 Racks OFF DECK + p200_RACKS_DROPPED = [] # List of P200 Racks DROPPED + SWAPSPOT = [] # List of Next Available Position for SWAP + REUSE_50_TIPS = [] # List of Next Available Position for SWAP + p50_INITIALTIPS = [] # List of Next Available Position for SWAP + REUSE_200_TIPS_1 = [] # List of Next Available Position for SWAP + REUSE_200_TIPS_2 = [] # List of Next Available Position for SWAP + p200_INITIALTIPS = [] # List of Next Available Position for SWAP + + def DefinePosition(tiptype, position, status): + # A Function that is called for potential tip Rack Position. Rather than defining tipracks at the beginning this function is called for each potential tip rack position, values are passed + # to the function and the tip position is added to the appropriate list as, Single Tip Pickup (STP), Reusable Tips, of left as OPEN which can be filled with p50 or p200 as needed. + global STP_50_TIPS + global STP_200_TIPS + global COLUMNS + global REUSE_ANY_50_TIPS + global REUSE_ANY_200_TIPS + global p50_RACK_COUNT + global p200_RACK_COUNT + global tiprack_200_STP + global tiprack_200_STR + global tiprack_50_R + global tiprack_200_R1 + global tiprack_200_R2 + if status == "OPEN": + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "STP_200" and STP_200_TIPS == True: + tiprack_200_STP = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_STP") + for i in range(1, 13): + STP_200_list_x4.append(tiprack_200_STP[f"E{i}"]) + STP_200_list_x4.append(tiprack_200_STP[f"A{i}"]) + rows = ["H", "G", "F", "E", "D", "C", "B", "A"] + columns = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] + for col in columns: + for row in rows: + STP_200_list_x1.append(tiprack_200_STP[row + col]) + if status == "STR_200" and STP_200_TIPS == True: + tiprack_200_STR = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_STR") + for i in range(1, 13): + STR_200_list.append(tiprack_200_STR[f"A{i}"]) + STR_200_list.append(tiprack_200_STR[f"E{i}"]) + if status == "STP_50" and STP_50_TIPS == True: + tiprack_50_STP = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_STP") + for i in range(1, 13): + STP_50_list_x4.append(tiprack_50_STP[f"E{i}"]) + STP_50_list_x4.append(tiprack_50_STP[f"A{i}"]) + rows = ["H", "G", "F", "E", "D", "C", "B", "A"] + columns = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] + for col in columns: + for row in rows: + STP_50_list_x1.append(tiprack_50_STP[row + col]) + if status == "STR_50" and STP_50_TIPS == True: + tiprack_50_STR = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_STR") + for i in range(1, 13): + STR_50_list.append(tiprack_50_STR[f"A{i}"]) + STR_50_list.append(tiprack_50_STR[f"E{i}"]) + if status == "REUSE_50_TIPS" and REUSE_ANY_50_TIPS == True: + p50_RACK_COUNT += 1 + tiprack_50_R = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_R") + protocol.comment(f"Adding tiprack_50_R") + for X in range(COLUMNS): + REUSE_50_TIPS.append(tiprack_50_R.wells_by_name().get(f"A{X+1}", None)) + for X in range(COLUMNS, 12): + p50_INITIALTIPS.append(tiprack_50_R.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_50_TIPS" and REUSE_ANY_50_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "REUSE_200_1TIPS" and REUSE_ANY_200_TIPS == True: + p200_RACK_COUNT += 1 + tiprack_200_R1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_R1") + protocol.comment(f"Adding tiprack_200_R1") + for X in range(COLUMNS): + REUSE_200_TIPS_1.append(tiprack_200_R1.wells_by_name().get(f"A{X+1}", None)) + for X in range(COLUMNS, 12): + p200_INITIALTIPS.append(tiprack_200_R1.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_200_1TIPS" and REUSE_ANY_200_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "REUSE_200_2TIPS" and REUSE_ANY_200_TIPS == True: + p200_RACK_COUNT += 1 + tiprack_200_R2 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_R2") + protocol.comment(f"Adding tiprack_200_R2") + for X in range(COLUMNS * 2): + REUSE_200_TIPS_2.append(tiprack_200_R2.wells_by_name().get(f"A{X+1}", None)) + for X in range(COLUMNS * 2, 12): + p200_INITIALTIPS.append(tiprack_200_R2.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_200_2TIPS" and REUSE_ANY_200_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + + def TipCheck(tiptype, tipuse, rep, tipset): + # A Function that is called replacing the Tip Pickup Step. Values are passed to the function such as tip type, reuse, and which rep (loop number) it is on. + # 1). Checks if there are 0 spaces available, if so, it clears the off deck positions allowing it to be refilled + # 2). Then, if its not Single Tip Pickup (which has its own separate list), if there are no positions available, and not a Reusing step, it adds a rack to either the On or Off Deck + # 3). If it is an Off Deck Position, it automatically starts the TipRackSwap Function, removing the next in line empry rack and swapping in the newly added Off Deck rack + global p50_TIPS + global p200_TIPS + global p50_RACK_COUNT + global p200_RACK_COUNT + global RESETCOUNT + global TIPDONEMODE + SOFTPAUSE = False + if len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) == 0: + for loop, X in enumerate(OFFDECK_LIST): + del protocol.deck[X] + if SOFTPAUSE == False: + protocol.pause("CLEARING OFFDECK POSITIONS") + protocol.comment("CLEARING OFFDECK POSITIONS") + AVAILABLE_POS_OFFDECK.append(X) + if tiptype == 50 and tipuse != "STP": + if p50_TIPS == 0 and len(p50_INITIALTIPS) == 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + if len(p50_RACKS_ONDECK) == 0: + protocol.comment("FIRST RACK") + else: + protocol.comment("IS THERE AN EMPTY RACK?") + RACKS_TO_DUMP.append(p50_RACKS_ONDECK[0]) + p50_RACKS_ONDECK.pop(0) + p50_RACK_COUNT += 1 + p50_TIPS += 12 + protocol.comment(f"Adding tiprack_50_{p50_RACK_COUNT}") + if len(AVAILABLE_POS_ONDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_50ul", AVAILABLE_POS_ONDECK[0], f"tiprack_50_{p50_RACK_COUNT}" + ) + AVAILABLE_POS_ONDECK.pop(0) + p50_RACKS_ONDECK.append(addtiprack) + p50_RACKS_PIPET.append(addtiprack) + p50.tip_racks = p50_RACKS_PIPET + elif len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_50ul", AVAILABLE_POS_OFFDECK[0], f"tiprack_50_{p50_RACK_COUNT}" + ) + AVAILABLE_POS_OFFDECK.pop(0) + p50_RACKS_OFFDECK.append(addtiprack) + p50_RACKS_PIPET.append(addtiprack) + p50.tip_racks = p50_RACKS_PIPET + TipRackSwap(50) + if p50_TIPS == 0 and len(p50_INITIALTIPS) > 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + p50.pick_up_tip(p50_INITIALTIPS[0]) + p50_INITIALTIPS.pop(0) + if p50_TIPS > 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + if len(p50_INITIALTIPS) > 0: + p50.pick_up_tip(p50_INITIALTIPS[0]) + p50_INITIALTIPS.pop(0) + else: + p50_TIPS -= 1 + p50.pick_up_tip() + if tiptype == 200 and tipuse != "STP": + if p200_TIPS == 0 and len(p200_INITIALTIPS) == 0: + if tipuse == "REUSE" and tipset == "WASH" and REUSE_200_TIPS_WASH == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + elif tipuse == "REUSE" and tipset == "ETOH" and REUSE_200_TIPS_ETOH == True: + p1000.pick_up_tip(REUSE_200_TIPS_2[rep]) + else: + if len(p200_RACKS_ONDECK) == 0: + protocol.comment("FIRST RACK") + else: + protocol.comment("IS THERE AN EMPTY RACK?") + RACKS_TO_DUMP.append(p200_RACKS_ONDECK[0]) + p200_RACKS_ONDECK.pop(0) + p200_RACK_COUNT += 1 + p200_TIPS += 12 + protocol.comment(f"Adding tiprack_200_{p200_RACK_COUNT}") + if len(AVAILABLE_POS_ONDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_200ul", AVAILABLE_POS_ONDECK[0], f"tiprack_200_{p200_RACK_COUNT}" + ) + AVAILABLE_POS_ONDECK.pop(0) + p200_RACKS_ONDECK.append(addtiprack) + p200_RACKS_PIPET.append(addtiprack) + p1000.tip_racks = p200_RACKS_PIPET + elif len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_200ul", AVAILABLE_POS_OFFDECK[0], f"tiprack_200_{p200_RACK_COUNT}" + ) + AVAILABLE_POS_OFFDECK.pop(0) + p200_RACKS_OFFDECK.append(addtiprack) + p200_RACKS_PIPET.append(addtiprack) + p1000.tip_racks = p200_RACKS_PIPET + TipRackSwap(200) + if p200_TIPS == 0 and len(p200_INITIALTIPS) > 0: + if tipuse == "REUSE" and REUSE_ANY_200_TIPS == True: + if tipset == "WASH" and REUSE_200_TIPS_WASH == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + elif tipset == "ETOH" and REUSE_200_TIPS_ETOH == True: + p1000.pick_up_tip(REUSE_200_TIPS_2[rep]) + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + else: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + if p200_TIPS > 0: + if tipuse == "REUSE" and REUSE_ANY_200_TIPS == True: + if tipset == "WASH" and REUSE_200_TIPS_WASH == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + elif tipset == "ETOH" and REUSE_200_TIPS_ETOH == True: + p1000.pick_up_tip(REUSE_200_TIPS_2[rep]) + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + if tiptype == 50 and tipuse == "STP": + p50.pick_up_tip(stp_50_list[rep]) + if tiptype == 200 and tipuse == "STP": + p1000.pick_up_tip(stp_200_list[rep]) + + def TipDone(tiptype, tipuse, rep, tipset): + # A Function that is called replacing the Tip dropping Step. Values are passed to the function such as tip type, reuse, and which rep (loop number) it is on. + # 1). Checks if it is a Single Tip Pickup, Reusable tip, or if the run is a Dry run, + global TIP_TRASH + if tiptype == 50: + if tipuse == "STP": + p50.drop_tip(str_50_list[rep]) if TIP_TRASH == False else p50.drop_tip() + elif tipuse == "REUSE": + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + if tiptype == 200: + if tipuse == "STP": + p1000.drop_tip(str_200_list[rep]) if TIP_TRASH == False else p1000.drop_tip() + elif tipuse == "REUSE": + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + + def TipRackSwap(tiptype): + # A Function that is called from within the TipCheck function to Swap Tip Racks. + # 1). Sets the values within the Function according to the appropriate tip rack list + # 2). If the Global Value of SWAPOFFDECK = True, it will swap tipracks (rather than dump into the Chute) + # 3). First in line of the RACKS_TO_DUMP is the one removed, can either be p50 or p200, no reusable tips or single Tip Racks + if tiptype == 50: + RACK_EMPTY = RACKS_TO_DUMP[0] + RACK_EMPTY_POS = RACKS_TO_DUMP[0].parent + RACK_NEW = p50_RACKS_OFFDECK[0] + RACK_NEW_POS = p50_RACKS_OFFDECK[0].parent + if tiptype == 200: + RACK_EMPTY = RACKS_TO_DUMP[0] + RACK_EMPTY_POS = RACKS_TO_DUMP[0].parent + RACK_NEW = p200_RACKS_OFFDECK[0] + RACK_NEW_POS = p200_RACKS_OFFDECK[0].parent + if SWAPOFFDECK == True: + SWAPSPOT.append(RACK_NEW_POS) + SWAPSPOT.append(RACK_EMPTY_POS) + protocol.comment("EMPTY POS " + str(SWAPSPOT[0])) + protocol.comment("RACK LEAVING THIS OFF DECK POS " + str(SWAPSPOT[1])) + protocol.comment("EMPTY RACK LEAVING THIS POS, MAKING IT THE NEW EMPTY POS " + str(SWAPSPOT[2])) + protocol.move_labware(labware=RACK_NEW, new_location=SWAPSPOT[0], use_gripper=USE_GRIPPER) + protocol.move_labware(labware=RACK_EMPTY, new_location=SWAPSPOT[1], use_gripper=USE_GRIPPER) + SWAPSPOT.pop(0) + SWAPSPOT.pop(0) + if tiptype == 50: + p50_RACKS_ONDECK.append(RACK_NEW) + RACKS_TO_DUMP.pop(0) + p50_RACKS_ONDECK.pop(0) + p50_RACKS_OFFDECK.pop(0) + if tiptype == 200: + p200_RACKS_ONDECK.append(RACK_NEW) + RACKS_TO_DUMP.pop(0) + p200_RACKS_ONDECK.pop(0) + p200_RACKS_OFFDECK.pop(0) + else: + SWAPS_POT = [RACK_EMPTY_POS] + protocol.move_labware(labware=RACK_EMPTY, new_location=TRASH, use_gripper=USE_GRIPPER) + protocol.move_labware(labware=RACK_NEW, new_location=SWAPS_POT[0], use_gripper=USE_GRIPPER) + if tiptype == 50: + p50_RACKS_ONDECK.append(RACK_NEW) + p50_RACKS_DROPPED.append(RACK_EMPTY) + RACKS_TO_DUMP.pop(0) + p50_RACKS_ONDECK.pop(0) + p50_RACKS_OFFDECK.pop(0) + if tiptype == 200: + p200_RACKS_ONDECK.append(RACK_NEW) + p200_RACKS_DROPPED.append(RACK_EMPTY) + RACKS_TO_DUMP.pop(0) + p200_RACKS_ONDECK.pop(0) + p200_RACKS_OFFDECK.pop(0) + + def PlateUnstack(Startposition, Stopposition): + # A Function that creates a plate, grips it based on offsets mimicking the stacked plates height, and moves it to a new position, + # This is a Standin Function until real functionality for plate unstacking is added. + global PLATE_STACKED + if PLATE_STACKED == 7: + sample_plate_1 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_1, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 6: + sample_plate_2 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_2, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 5: + sample_plate_3 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_3, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 4: + sample_plate_4 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_4, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 3: + sample_plate_5 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_5, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 2: + sample_plate_6 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_6, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 1: + sample_plate_7 = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_7, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": 0}, + drop_offset=deck_drop_offset, + ) + + # ======================= SIMPLE SETUP ARRANGEMENT ====================== + # This is a condensed, simpler deck layout arrangement based on position. There are 2 sections, one with all the modules on deck (the NGS Workstation setup) and one without. + # This uses the DefinePosition function listed earlier, it asks for: tiptype (None, 50 or 200), position ('A1', etc.), and Status ('OPEN' for any tip, or the special uses as below) + # List all empty positions avaiable. + # DefinePosition(None,'A2','OPEN') <-- Basic, open for either tip type + # DefinePosition(None,'A2','CLOSED') <-- Tip Location is closed, used just for keeping track for the user + # DefinePosition(200,'A2','REUSE_200_1TIPS') <-- Reusable 200 tips + # DefinePosition(200,'A2','STP_200') <-- Single Tip Pickup 200 tips + # DefinePosition(200,'A2','STR_200') <-- Single Tip Return for 200 tips (testing purposes) + # Then there is a block of code for whether or not the trash is a CHUTE or BIN, note that with a BIN position A4 is not available. + + # ========== FIRST ROW =========== + if ONDECK_THERMO == True: + thermocycler = protocol.load_module("thermocycler module gen2") + reagent_thermo = thermocycler.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") + else: + DefinePosition(None, "A1", "OPEN") + DefinePosition(None, "A2", "OPEN") + # ========== SECOND ROW ========== + if ONDECK_THERMO == True: + pass + else: + reagent_thermo = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "A1") + if FORMAT == "96": + qpcrplate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "B2") + if FORMAT == "384": + qpcrplate = protocol.load_labware("appliedbiosystemsmicroamp_384_wellplate_40ul", "B2") + DefinePosition(None, "B3", "OPEN") + # ========== THIRD ROW =========== + if ONDECK_TEMP == True: + temp_block = protocol.load_module("temperature module gen2", "C1") + temp_adapter = temp_block.load_adapter("opentrons_96_well_aluminum_block") + mix_plate = temp_adapter.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") + else: + mix_plate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "C1") + reservoir = ( + protocol.load_labware("nest_12_reservoir_15ml", "C2", "Reservoir") + if RES_TYPE_96x == False + else protocol.load_labware("nest_96_wellplate_2ml_deep", "C2") + ) + DefinePosition(None, "C3", "OPEN") + # ========== FOURTH ROW ========== + if ONDECK_HEATERSHAKER == True: + heatershaker = protocol.load_module("heaterShakerModuleV1", "D1") + dilution_plate = heatershaker.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt") + else: + dilution_plate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "D1") + source_plate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "D2") + # ============ TRASH ============= + if TRASH_POSITION == "BIN": + TRASH = protocol.load_trash_bin("A3") + DefinePosition(None, "D3", "OPEN") + DefinePosition(None, "B4", "OPEN") + DefinePosition(None, "B4", "OPEN") + DefinePosition(None, "C4", "OPEN") + DefinePosition(None, "D4", "OPEN") + OFFDECK_LIST = ["B4", "C4", "D4"] + if TRASH_POSITION == "CHUTE": + TRASH = protocol.load_waste_chute() + DefinePosition(None, "A3", "OPEN") + DefinePosition(None, "A4", "OPEN") + DefinePosition(None, "B4", "OPEN") + DefinePosition(None, "C4", "OPEN") + DefinePosition(None, "D4", "OPEN") + OFFDECK_LIST = ["A4", "B4", "C4", "D4"] + + # If SWAPOFFDECK = True (Meaning swapping empty On Deck Racks with New Off Deck Racks), removes the first listed tip position to keep it empty for temporary space. + if SWAPOFFDECK == True: + SWAPSPOT.append(AVAILABLE_POS_ONDECK[0]) + AVAILABLE_POS_ONDECK.pop(0) + + # Reverse the lists of Positions for accessibility (First Checked On Deck Slot is D4, Off Deck is D4) + AVAILABLE_POS_ONDECK.reverse() + AVAILABLE_POS_OFFDECK.reverse() + OFFDECK_LIST.reverse() + + # =========================== REAGENT PLATE ============================= + STD = reagent_thermo["A1"] + qPCR = reagent_thermo["A3"] + + # ============================ RESERVOIR ================================ + DIL = reservoir["A1"] + + # ======================= TIP AND SAMPLE TRACKING ======================= + # This is a list of each column to be used in the protocol, as well as any intermediate or final sample positions. + # column_1_list = [f'A{i}' for i in range(1, COLUMNS + 1)] <-- This is a Simple list of 'A1' through 'A12', meaning a full plate. + # Example Protocols can look like this: + # if COLUMNS == 3: + # column_1_list = ['A1','A2','A3'] <-- Initial 3 columns of Samples + # column_2_list = ['A4','A5','A6'] <-- Final 3 columns of Samples + column_1_list = ["A" + INICOLUMN1, "A" + INICOLUMN2, "A" + INICOLUMN3, "A" + INICOLUMN4, "A" + INICOLUMN5, "A" + INICOLUMN6] + column_DIL1_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Dilution 1/20 wells + column_DIL2_list = ["A7", "A8", "A9", "A10", "A11", "A12"] # Dilution 1/50 wells + column_3_list = ["A2", "A3", "A4", "A5", "A6", "A7"] # qPCR to Mix plate + column_4_list = ["A2", "A3", "A4", "A5", "A6", "A7"] # Diluted Samples to Mix (Dilution Plate to Mix Plate) (Skipping A1 for Standards) + column_5_list = [ + ["A1", "A2", "B1", "B2"], + ["A3", "A4", "B3", "B4"], + ["A5", "A6", "B5", "B6"], + ["A7", "A8", "B7", "B8"], + ["A9", "A10", "B9", "B10"], + ["A11", "A12", "B11", "B12"], + ["A13", "A14", "B13", "B14"], + ] + + # ============================ CUSTOM OFFSETS =========================== + # These are Custom Offsets which are a PER INSTRUMENT Setting, to account for slight adjustments of the gripper calibration or labware. + if CUSTOM_OFFSETS == True: + PCRPlate_Z_offset = 0 + Deepwell_Z_offset = 1 + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": -2, "z": 0} + hs_pick_up_offset = {"x": 0, "y": -2, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 0.0, "z": 0.5} + mb_pick_up_offset = {"x": 0, "y": -2, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": 0, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # DECK OFFSETS + deck_drop_offset = {"x": 0, "y": 0, "z": 0} + deck_pick_up_offset = {"x": 0, "y": 0, "z": 0} + else: + PCRPlate_Z_offset = 0 + Deepwell_Z_offset = 0 + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": 0, "z": 0} + hs_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 0.0, "z": 0} + mb_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": 0, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # DECK OFFSETS + deck_drop_offset = {"x": 0, "y": 0, "z": 0} + deck_pick_up_offset = {"x": 0, "y": 0, "z": 0} + + # ================================================================================================= + # ========================================= PROTOCOL START ======================================== + # ================================================================================================= + if ONDECK_THERMO == True: + thermocycler.open_lid() + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + if DRYRUN == False: + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + if ONDECK_THERMO == True: + thermocycler.set_block_temperature(4) + if ONDECK_TEMP == True: + temp_block.set_temperature(4) + protocol.pause("Ready") + if ONDECK_HEATERSHAKER == True: + heatershaker.close_labware_latch() + # ================================================================================================= + # ========================================= PROTOCOL START ======================================== + # ================================================================================================= + + if STEP_DILUTE == True: + protocol.comment("==============================================") + protocol.comment("--> Diluting Sample") + protocol.comment("==============================================") + + protocol.comment("--> Adding Diluent") + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default + # =============================================== + TipCheck(200, None, 1, None) + for loop, X in enumerate(column_1_list): + p1000.aspirate(200, DIL.bottom(z=2)) + p1000.dispense(98, dilution_plate[column_DIL1_list[loop]].bottom(z=0.3)) + p1000.dispense(95, dilution_plate[column_DIL2_list[loop]].bottom(z=0.3)) + p1000.move_to(DIL.top()) + p1000.blow_out() + if loop == COLUMNS - 1: + break + TipDone(200, None, 1, None) + # =============================================== + + protocol.comment("--> Adding Sample to Diluent 1") + SampleVol = 2 + DilMixRPM = 1200 + DilMixTime = 2 * 60 if DRYRUN == False else 0.1 * 60 + # p50.configure_for_volume(2) + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for loop, X in enumerate(column_1_list): + TipCheck(50, None, loop, None) + p50.aspirate(SampleVol + 3, source_plate.wells_by_name()[X].bottom(z=1)) + p50.dispense(3, source_plate.wells_by_name()[X].bottom(z=1)) + p50.aspirate(SampleVol + 3, source_plate.wells_by_name()[X].bottom(z=1)) + p50.dispense(3, source_plate.wells_by_name()[X].bottom(z=1)) + p50.dispense(SampleVol + 1, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + if TIP_MIX == True: + p50.mix(2, 10, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + else: + p50.mix(10, 45, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.blow_out(dilution_plate.wells_by_name()[column_DIL1_list[loop]].top(z=-2)) + TipDone(50, None, loop, None) + if loop == COLUMNS - 1: + break + # =============================================== + if ONDECK_HEATERSHAKER == True: + heatershaker.set_and_wait_for_shake_speed(rpm=DilMixRPM) + protocol.delay(DilMixTime) + heatershaker.deactivate_shaker() + + protocol.comment("--> Adding Sample to Diluent 2") + SampleVol = 5 + DilMixRPM = 1200 + DilMixTime = 2 * 60 if DRYRUN == False else 0.1 * 60 + # p50.configure_for_volume(5) + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for loop, X in enumerate(column_1_list): + TipCheck(50, None, loop, None) + p50.aspirate(SampleVol + 3, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.dispense(3, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.aspirate(SampleVol + 3, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.dispense(3, dilution_plate.wells_by_name()[column_DIL1_list[loop]].bottom(z=1)) + p50.dispense(SampleVol + 1, dilution_plate.wells_by_name()[column_DIL2_list[loop]].bottom(z=1)) + if TIP_MIX == True: + p50.mix(2, 10, dilution_plate.wells_by_name()[column_DIL2_list[loop]].bottom(z=1)) + else: + p50.mix(10, 45, dilution_plate.wells_by_name()[column_DIL2_list[loop]].bottom(z=1)) + p50.blow_out(dilution_plate.wells_by_name()[column_DIL2_list[loop]].top(z=-2)) + TipDone(50, None, loop, None) + if loop == COLUMNS - 1: + break + # =============================================== + if ONDECK_HEATERSHAKER == True: + heatershaker.set_and_wait_for_shake_speed(rpm=DilMixRPM) + protocol.delay(DilMixTime) + heatershaker.deactivate_shaker() + + if STEP_MIX == True: + protocol.comment("==============================================") + protocol.comment("--> Adding qPCR Mix") + protocol.comment("==============================================") + qPCRVol = 27 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + TipCheck(50, None, loop, None) + p50.aspirate(qPCRVol, qPCR.bottom(z=1)) + p50.dispense(qPCRVol, mix_plate.wells_by_name()["A1"].bottom(z=0.3)) + p50.default_speed = 50 + p50.move_to(mix_plate.wells_by_name()[X].top(z=-1)) + protocol.delay(seconds=2) + p50.blow_out(mix_plate.wells_by_name()[X].top(z=-1)) + for loop, X in enumerate(column_3_list): + p50.aspirate(qPCRVol, qPCR.bottom(z=1)) + p50.dispense(qPCRVol, mix_plate.wells_by_name()[X].bottom(z=0.3)) + p50.default_speed = 50 + p50.move_to(mix_plate.wells_by_name()[X].top(z=-1)) + protocol.delay(seconds=2) + p50.blow_out(mix_plate.wells_by_name()[X].top(z=-1)) + p50.default_speed = 400 + if loop == COLUMNS - 1: + break + TipDone(50, None, loop, None) + # =============================================== + + protocol.comment("==============================================") + protocol.comment("--> Adding Standards to Mix") + protocol.comment("==============================================") + SampleVol = 18 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + TipCheck(50, None, loop, None) + p50.aspirate(SampleVol, STD.bottom(z=0.3)) + p50.dispense(SampleVol, mix_plate["A1"].bottom(z=0.3)) + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.mix(5, 30, mix_plate["A1"].bottom(z=1)) + p50.default_speed = 50 + p50.move_to(mix_plate["A1"].top(z=-1)) + protocol.delay(seconds=2) + p50.blow_out(mix_plate["A1"].top(z=-1)) + p50.default_speed = 400 + TipDone(50, None, loop, None) + # =============================================== + + protocol.comment("==============================================") + protocol.comment("--> Adding Diluted Sample to Mix") + protocol.comment("==============================================") + SampleVol = 18 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for X in range(COLUMNS): + TipCheck(50, None, loop, None) + p50.aspirate(SampleVol, dilution_plate.wells_by_name()[column_DIL2_list[X]].bottom(z=1)) + p50.dispense(SampleVol, mix_plate.wells_by_name()[column_4_list[X]].bottom(z=0.5)) + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.mix(5, 30, mix_plate.wells_by_name()[column_4_list[X]].bottom(z=1)) + p50.default_speed = 50 + p50.move_to(mix_plate.wells_by_name()[column_4_list[X]].top(z=-1)) + protocol.delay(seconds=2) + p50.blow_out(mix_plate.wells_by_name()[column_4_list[X]].top(z=-1)) + p50.default_speed = 400 + TipDone(50, None, loop, None) + if loop == (COLUMNS) - 1: + break + # =============================================== + + if STEP_DISPENSE == True: + if FORMAT == "96": + protocol.comment("==============================================") + protocol.comment("--> Dispensing 96 well") + protocol.comment("==============================================") + MixqPCRVol = 40 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for loop, X in enumerate(column_3_list): + TipCheck(50, None, loop, None) + p50.mix(5, MixqPCRVol - 5, mix_plate[X].bottom(z=1)) + p50.aspirate(MixqPCRVol + 2, mix_plate[X].bottom(z=0.3)) + protocol.delay(seconds=0.2) + # =============================================== + p50.move_to(qpcrplate[X].center()) + p50.default_speed = 100 + p50.dispense(MixqPCRVol, qpcrplate[X].bottom(z=1)) + protocol.delay(seconds=0.2) + p50.move_to(qpcrplate[X].top(z=-1)) + p50.default_speed = 400 + # =============================================== + TipDone(50, None, loop, None) + if loop == (COLUMNS + 1) - 1: + break + # =============================================== + + if FORMAT == "384": + protocol.comment("==============================================") + protocol.comment("--> Dispensing 384 well") + protocol.comment("==============================================") + MixqPCRVol = 40 + Multidispense = [10.1, 10.2, 9.8, 9.9] # Slight Volume Changes to account for Multidispense Variation + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.25 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.25 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default + # =============================================== + for loop, X in enumerate(column_3_list): + TipCheck(50, None, loop, None) + p50.mix(5, MixqPCRVol - 5, mix_plate[X].bottom(z=1)) + p50.aspirate(MixqPCRVol + 5, mix_plate[X].bottom(z=0.3)) + p50.dispense(2, mix_plate[X].bottom(z=0.3)) + protocol.delay(seconds=0.2) + # =============================================== + for loop2, X in enumerate(column_5_list[loop]): + p50.move_to(qpcrplate[X].top(z=1.0)) + protocol.delay(seconds=0.2) + p50.default_speed = 10 + p50.move_to(qpcrplate[X].center()) + p50.default_speed = 2.5 + p50.dispense(Multidispense[loop2], qpcrplate[X].bottom(z=1)) + protocol.delay(seconds=0.2) + p50.default_speed = 100 + # =============================================== + p50.default_speed = 400 + TipDone(50, None, loop, None) + if loop == (COLUMNS + 1) - 1: + break + # =============================================== + + # ================================================================================================= + # ========================================== PROTOCOL END ========================================= + # ================================================================================================= + if DEACTIVATE_TEMP == True: + if ONDECK_THERMO == True: + thermocycler.deactivate_block() + if ONDECK_TEMP == True: + temp_block.deactivate() + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + # ================================================================================================= + # ========================================== PROTOCOL END ========================================= + # ================================================================================================= + + protocol.comment("==============================================") + protocol.comment("--> Report") + protocol.comment("==============================================") + # This is a section that will print out the various lists to help keep track of modifying the protocol, set the REPORT step to False to ignore. + if REPORT == True: + protocol.comment("REUSE_50_TIPS " + str(REUSE_50_TIPS)) + protocol.comment("p50_INITIALTIPS " + str(p50_INITIALTIPS)) + protocol.comment("REUSE_200_TIPS_1 " + str(REUSE_200_TIPS_1)) + protocol.comment("REUSE_200_TIPS_2 " + str(REUSE_200_TIPS_2)) + protocol.comment("p200_INITIALTIPS " + str(p200_INITIALTIPS)) + protocol.comment("SWAPSPOT " + str(SWAPSPOT)) + protocol.comment("AVAILABLE_POS_ONDECK " + str(AVAILABLE_POS_ONDECK)) + protocol.comment("AVAILABLE_POS_OFFDECK " + str(AVAILABLE_POS_OFFDECK)) + protocol.comment("REUSE_50_TIPS_COUNT " + str(REUSE_50_TIPS_COUNT)) + protocol.comment("REUSE_200_TIPS_COUNT " + str(REUSE_200_TIPS_COUNT)) + protocol.comment("p50_RACKS_ONDECK " + str(p50_RACKS_ONDECK)) + protocol.comment("p50_RACKS_OFFDECK " + str(p50_RACKS_OFFDECK)) + protocol.comment("p50_RACKS_DROPPED " + str(p50_RACKS_DROPPED)) + protocol.comment("p50_TIPS " + str(p50_TIPS)) + protocol.comment("p50_RACKS_PIPET " + str(p50_RACKS_PIPET)) + protocol.comment("p200_RACKS_ONDECK " + str(p200_RACKS_ONDECK)) + protocol.comment("p200_RACKS_OFFDECK " + str(p200_RACKS_OFFDECK)) + protocol.comment("p200_RACKS_DROPPED " + str(p200_RACKS_DROPPED)) + protocol.comment("p200_TIPS " + str(p200_TIPS)) + protocol.comment("p200_RACKS_PIPET " + str(p200_RACKS_PIPET)) + protocol.comment("RACKS_TO_DUMP " + str(RACKS_TO_DUMP)) + + # This is a section that is used to define liquids, and label wells, this is optional, and unconnected from the rest of the protocol, used only for the App and Website + # This is at the end because it adds lines of code to the runtime that can be at the end rather than the beginning, since it has no effect on the protocol setps. + if LABEL == True: + # PROTOCOL SETUP - LABELING + + # ======== ESTIMATING LIQUIDS ======= + Sample_Volume = 50 + STD_Volume = 50 + qPCR_Volume = 200 + DIL_Volume = 15000 + + TotalColumn = ["A", "B", "C", "D", "E", "F", "G", "H"] + UsedColumn = ["A", "B", "C", "D", "E", "F", "G", "H"] + TotalColumn2 = ["I", "J", "K", "L", "M", "N", "O", "P"] + + # ======== DEFINING LIQUIDS ======= + DIL = protocol.define_liquid(name="DIL", description="Dilution Buffer", display_color="#00FFF2") # 00FFF2 = 'Base Light Blue' + Liquid_trash_well = protocol.define_liquid( + name="Liquid_trash_well", description="Liquid Trash", display_color="#9B9B9B" + ) # 9B9B9B = 'Liquid Trash Grey' + Sample = protocol.define_liquid(name="Sample", description="Sample", display_color="#52AAFF") # 52AAFF = 'Sample Blue' + Final_Sample = protocol.define_liquid( + name="Final_Sample", description="Final Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + qPCR = protocol.define_liquid(name="qPCR", description="qPCR Mix", display_color="#FF0000") # FF0000 = 'Base Red' + STD = protocol.define_liquid(name="STD", description="qPCR Standards", display_color="#FFA000") # FFA000 = 'Base Orange' + Final_Sample = protocol.define_liquid( + name="Final_Sample", description="Final Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + Placeholder_Sample = protocol.define_liquid( + name="Placeholder_Sample", description="Placeholder Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + # ======== LOADING LIQUIDS ======= + if RES_TYPE_96x == False: + reservoir.wells_by_name()["A1"].load_liquid(liquid=DIL, volume=DIL_Volume * 8) + reservoir.wells_by_name()["A12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if RES_TYPE_96x == True: + for loop, X in enumerate(UsedColumn): + reservoir.wells_by_name()[X + "1"].load_liquid(liquid=DIL, volume=DIL_Volume) + reservoir.wells_by_name()[X + "12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if COLUMNS >= 1: + for loop, X in enumerate(TotalColumn): + reagent_thermo.wells_by_name()[X + "1"].load_liquid(liquid=STD, volume=STD_Volume) + reagent_thermo.wells_by_name()[X + "3"].load_liquid(liquid=qPCR, volume=qPCR_Volume) + mix_plate.wells_by_name()[X + "1"].load_liquid(liquid=Placeholder_Sample, volume=0) + mix_plate.wells_by_name()[X + "2"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "1"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "7"].load_liquid(liquid=Placeholder_Sample, volume=0) + if FORMAT == "96": + qpcrplate.wells_by_name()[X + "1"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[X + "2"].load_liquid(liquid=Final_Sample, volume=10) + if FORMAT == "384": + qpcrplate.wells_by_name()[X + "1"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "1"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[X + "2"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[X + "3"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[X + "4"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "2"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "3"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "4"].load_liquid(liquid=Final_Sample, volume=10) + source_plate.wells_by_name()[X + INICOLUMN1].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS >= 2: + for loop, X in enumerate(TotalColumn): + mix_plate.wells_by_name()[X + "2"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "2"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "8"].load_liquid(liquid=Placeholder_Sample, volume=0) + if FORMAT == "96": + qpcrplate.wells_by_name()[X + "3"].load_liquid(liquid=Final_Sample, volume=10) + if FORMAT == "384": + qpcrplate.wells_by_name()[X + "5"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[X + "6"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "5"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "6"].load_liquid(liquid=Final_Sample, volume=10) + source_plate.wells_by_name()[X + INICOLUMN2].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS >= 3: + for loop, X in enumerate(TotalColumn): + mix_plate.wells_by_name()[X + "3"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "3"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "9"].load_liquid(liquid=Placeholder_Sample, volume=0) + if FORMAT == "96": + qpcrplate.wells_by_name()[X + "4"].load_liquid(liquid=Final_Sample, volume=10) + if FORMAT == "384": + qpcrplate.wells_by_name()[X + "7"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[X + "8"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "7"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "8"].load_liquid(liquid=Final_Sample, volume=10) + source_plate.wells_by_name()[X + INICOLUMN3].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS >= 4: + for loop, X in enumerate(TotalColumn): + mix_plate.wells_by_name()[X + "4"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "4"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "10"].load_liquid(liquid=Placeholder_Sample, volume=0) + if FORMAT == "96": + qpcrplate.wells_by_name()[X + "5"].load_liquid(liquid=Final_Sample, volume=10) + if FORMAT == "384": + qpcrplate.wells_by_name()[X + "9"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[X + "10"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "9"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "10"].load_liquid(liquid=Final_Sample, volume=10) + source_plate.wells_by_name()[X + INICOLUMN4].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS >= 5: + for loop, X in enumerate(TotalColumn): + mix_plate.wells_by_name()[X + "5"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "5"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "11"].load_liquid(liquid=Placeholder_Sample, volume=0) + if FORMAT == "96": + qpcrplate.wells_by_name()[X + "6"].load_liquid(liquid=Final_Sample, volume=10) + if FORMAT == "384": + qpcrplate.wells_by_name()[X + "11"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[X + "12"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "11"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "12"].load_liquid(liquid=Final_Sample, volume=10) + source_plate.wells_by_name()[X + INICOLUMN5].load_liquid(liquid=Sample, volume=Sample_Volume) + if COLUMNS >= 6: + for loop, X in enumerate(TotalColumn): + mix_plate.wells_by_name()[X + "6"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "6"].load_liquid(liquid=Placeholder_Sample, volume=0) + dilution_plate.wells_by_name()[X + "12"].load_liquid(liquid=Placeholder_Sample, volume=0) + if FORMAT == "96": + qpcrplate.wells_by_name()[X + "7"].load_liquid(liquid=Final_Sample, volume=10) + if FORMAT == "384": + qpcrplate.wells_by_name()[X + "13"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[X + "14"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "13"].load_liquid(liquid=Final_Sample, volume=10) + qpcrplate.wells_by_name()[TotalColumn2[loop] + "14"].load_liquid(liquid=Final_Sample, volume=10) + source_plate.wells_by_name()[X + INICOLUMN6].load_liquid(liquid=Sample, volume=Sample_Volume) diff --git a/app-testing/files/protocols/pl_M_N_Nucleomag_DNA_Flex_multi.py b/app-testing/files/protocols/pl_M_N_Nucleomag_DNA_Flex_multi.py new file mode 100644 index 00000000000..fcd44a52aa6 --- /dev/null +++ b/app-testing/files/protocols/pl_M_N_Nucleomag_DNA_Flex_multi.py @@ -0,0 +1,653 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"Macherey_Nagel_DW":false,"dry_run":false,"inc_lysis":true,"mount":"left","temp_mod":true,"heater_shaker":true,"res_type":"nest_12_reservoir_15ml","num_samples":8,"wash1_vol":600,"wash2_vol":600,"wash3_vol":600,"wash4_vol":900,"lysis_vol":200,"bind_vol":360,"elution_vol":100,"protocol_filename":"M-N_Nucleomag_DNA-Flex_multi"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +import json +import math +from opentrons import types +import numpy as np + +metadata = {"author": "Zach Galluzzo "} + +requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +""" +Here is where you can modify the magnetic module engage height: +""" +whichwash = 1 +sample_max = 48 +tip1k = 0 +tipsuper = 0 +drop_count = 0 +waste_vol = 0 + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + + # Hard Coded Functions + trash_chute = False # If false, waste bin loaded in D3, if True, trash chute loaded there + USE_GRIPPER = True + Macherey_Nagel_DW = False + dry_run = False + inc_lysis = True + mount = "left" + res_type = "nest_12_reservoir_15ml" + temp_mod = True # True or false if you have a temp mod loaded on deck with the elution plate + heater_shaker = True + + num_samples = 48 + wash1_vol = 600 + wash2_vol = 600 + wash3_vol = 600 + wash4_vol = 900 + lysis_vol = 200 + bind_vol = 360 + elution_vol = 100 + + try: + [ + res_type, + temp_mod, + trash_chute, + USE_GRIPPER, + Macherey_Nagel_DW, + dry_run, + inc_lysis, + mount, + num_samples, + heater_shaker, + wash1_vol, + wash2_vol, + wash3_vol, + wash4_vol, + lysis_vol, + bind_vol, + elution_vol, + ] = get_values( # noqa: F821 + "res_type", + "temp_mod", + "trash_chute", + "USE_GRIPPER", + "Macherey_Nagel_DW", + "dry_run", + "inc_lysis", + "mount", + "num_samples", + "heater_shaker", + "wash1_vol", + "wash2_vol", + "wash3_vol", + "wash4_vol", + "lysis_vol", + "bind_vol", + "elution_vol", + ) + + except NameError: + pass + + # Protocol Parameters + if Macherey_Nagel_DW: + deepwell_type = "macherey_nagel_dwplate_2200ul" + else: + deepwell_type = "nest_96_wellplate_2ml_deep" + if not dry_run: + settling_time = 3.5 + lysis_incubation = 80 + else: + settling_time = 0.25 + lysis_incubation = 0.25 + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + res_type = "nest_12_reservoir_15ml" + sample_vol = 10 # Sample should be pelleted tissue/bacteria/cells + PK_vol = bead_vol = 25 + lysis_total_vol = lysis_vol + PK_vol + starting_vol = lysis_total_vol + sample_vol + binding_buffer_vol = bind_vol + bead_vol + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + if Macherey_Nagel_DW: + h_s_adapter = h_s.load_adapter("opentrons_universal_flat_adapter") + else: + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") + h_s.close_labware_latch() + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Sample Plate") + + if temp_mod: + temp = ctx.load_module("temperature module gen2", "A3") + temp_block = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = temp_block.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate") + + magblock = ctx.load_module("magneticBlockV1", "C1") + waste = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste").wells()[0].top() + res1 = ctx.load_labware(res_type, "D2", "reagent reservoir 1") + res2 = ctx.load_labware(res_type, "C2", "reagent reservoir 2") + num_cols = math.ceil(num_samples / 8) + + # Load tips and combine all similar boxes + tips1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "Tips 1") + tips1001 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "Tips 2") + tips1002 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B1", "Tips 3") + tips = [*tips1000.wells()[8 * (num_cols) : 96], *tips1001.wells(), *tips1002.wells()] + tips_sn = tips1000.wells()[: 8 * (num_cols)] + + # load instruments + m1000 = ctx.load_instrument("flex_8channel_1000", mount) + + """ + Here is where you can define the locations of your reagents. + """ + lysis_ = res1.wells()[0] + binding_buffer = res1.wells()[1:3] + wash1 = res1.wells()[3:6] + wash2 = res1.wells()[6:9] + wash3 = res1.wells()[9:] + wash4 = res2.wells()[:6] + elution_solution = res2.wells()[-1] + + samples_m = sample_plate.rows()[0][:num_cols] + elution_samples_m = elutionplate.rows()[0][:num_cols] + # Recreate for liquid definitions (per well) + samps = sample_plate.wells()[: (8 * num_cols)] + elution_samps = elutionplate.wells()[: (8 * num_cols)] + + colors = [ + "#008000", + "#008000", + "#A52A2A", + "#A52A2A", + "#00FFFF", + "#0000FF", + "#800080", + "#ADD8E6", + "#FF0000", + "#FFFF00", + "#FF00FF", + "#00008B", + "#7FFFD4", + "#FFC0CB", + "#FFA500", + "#00FF00", + "#C0C0C0", + ] + + # Begin with assigning plate wells before reservoir wells + samples = ctx.define_liquid(name="Samples", description="Cell Pellet", display_color="#FFA500") + + for well in samps: + well.load_liquid(liquid=samples, volume=0) + + locations = [lysis_, lysis_, binding_buffer, binding_buffer, wash1, wash2, wash3, wash4, elution_solution] + vols = [lysis_vol, PK_vol, bead_vol, bind_vol, wash1_vol, wash2_vol, wash3_vol, wash4_vol, elution_vol] + liquids = ["Lysis", "PK", "Beads", "Binding", "Wash 1", "Wash 2", "Wash 3", "Wash 4", "Elution"] + + delete = len(colors) - len(liquids) + + if delete >= 1: + for i in range(delete): + colors.pop(-1) + + def liquids_(liq, location, color, vol): + sampnum = 8 * (math.ceil(num_samples / 8)) + """ + Takes an individual liquid at a time and adds the color to the well + in the description. + """ + # Volume Calculation + if liq == "PK": + extra_samples = math.ceil(1500 / lysis_vol) + + elif liq == "Beads": + extra_samples = math.ceil(1500 / bind_vol) + + else: + extra_samples = math.ceil(1500 / vol) + + # Defining and assigning liquids to wells + if isinstance(location, list): + limit = sample_max / len(location) # Calculates samples/ res well + iterations = math.ceil(sampnum / limit) + left = sampnum - limit + while left > limit: + left = left - limit + if left > 0: + last_iteration_samp_num = left + elif left < 0: + last_iteration_samp_num = sampnum + else: + last_iteration_samp_num = limit + + samples_per_well = [] + + for i in range(iterations): + # append the left over on the last iteration + if i == (iterations - 1): + samples_per_well.append(last_iteration_samp_num) + else: + samples_per_well.append(limit) + + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + for sample, well in zip(samples_per_well, location[: len(samples_per_well)]): + v = vol * (sample + extra_samples) + well.load_liquid(liquid=liq, volume=v) + + else: + v = vol * (sampnum + extra_samples) + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + location.load_liquid(liquid=liq, volume=v) + + for x, (ll, l, c, v) in enumerate(zip(liquids, locations, colors, vols)): + liquids_(ll, l, c, v) + + m1000.flow_rate.aspirate = 300 + m1000.flow_rate.dispense = 300 + m1000.flow_rate.blow_out = 300 + + def tiptrack(pip, tipbox): + global tip1k + global tipsuper + global drop_count + if tipbox == tips: + m1000.pick_up_tip(tipbox[int(tip1k)]) + tip1k = tip1k + 8 + drop_count = drop_count + 8 + + if tipbox == tips_sn: + m1000.pick_up_tip(tipbox[int(tipsuper)]) + tipsuper = tipsuper + 8 + if tipsuper == num_cols * 8: + tipsuper = 0 + + if drop_count >= 250: + drop_count = 0 + ctx.pause("Please empty the waste bin of all the tips before continuing.") + + def blink(): + for i in range(3): + ctx.set_rail_lights(True) + ctx.delay(minutes=0.01666667) + ctx.set_rail_lights(False) + ctx.delay(minutes=0.01666667) + + def _waste_track(vol): + global waste_vol + waste_vol = waste_vol + (vol * 8) + ctx.comment("Waste Volume = " + str(waste_vol)) + if waste_vol >= 180000: + blink() + ctx.pause("Please empty liquid waste before resuming") + waste_vol = 0 + + def remove_supernatant(vol): + ctx.comment("-----Removing Supernatant-----") + m1000.flow_rate.aspirate = 20 + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + m1000.flow_rate.aspirate = 50 + for i, m in enumerate(samples_m): + m1000.pick_up_tip(tips_sn[8 * i]) + loc = m.bottom(0.5) + for _ in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, m.top()) + m1000.move_to(m.center()) + _waste_track(vol) + m1000.transfer(vol_per_trans, loc, waste, new_tip="never", air_gap=20) + m1000.air_gap(20) + m1000.drop_tip(tips_sn[8 * i]) + m1000.flow_rate.aspirate = 300 + + def bead_mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top().move(types.Point(x=0, y=0, z=5)) + aspbot = well.bottom().move(types.Point(x=0, y=1.75, z=1)) + asptop = well.bottom().move(types.Point(x=0, y=-1.75, z=5)) + disbot = well.bottom().move(types.Point(x=0, y=1.75, z=3)) + distop = well.top().move(types.Point(x=0, y=1, z=0)) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, aspbot) + pip.dispense(vol, distop) + pip.aspirate(vol, asptop) + pip.dispense(vol, disbot) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 20 + pip.aspirate(vol, aspbot) + pip.dispense(vol, aspbot) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(1) + disp = well.top(-15) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def lysis(vol, source): + num_transfers = math.ceil(vol / 980) + tiptrack(m1000, tips) + for i in range(num_cols): + src = source + tvol = vol / num_transfers + mixvol = num_cols * vol + if mixvol > 1000: + mixvol = 1000 + if i == 0: + for x in range(3 if not dry_run else 1): + m1000.aspirate(mixvol, src.bottom()) + m1000.dispense(mixvol, src.bottom(20)) + for t in range(num_transfers): + m1000.aspirate(tvol, src.bottom(1)) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, samples_m[i].top()) + + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + for mix in range(10 if not dry_run else 1): + m1000.aspirate(190, samples_m[i]) + m1000.dispense(190, samples_m[i].bottom(25)) + m1000.flow_rate.aspirate = 20 + m1000.flow_rate.dispense = 20 + m1000.aspirate(190, samples_m[i]) + m1000.dispense(190, samples_m[i].bottom(10)) + m1000.flow_rate.aspirate = 300 + m1000.flow_rate.dispense = 300 + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + h_s.set_and_wait_for_temperature(56) + ctx.delay(minutes=lysis_incubation if not dry_run else 0.25, msg="Shake at 1800 rpm for " + str(lysis_incubation) + " minutes.") + h_s.deactivate_shaker() + h_s.deactivate_heater() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 56C and 2000 rpm for " + str(lysis_incubation) + " minutes.") + + def bind(vol): + """ + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead bining. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + + ctx.comment("-----Beginning Bind Steps-----") + for i, well in enumerate(samples_m): + if num_cols > 4: + if i == 0 or i == 3: + mixvol = 2 * vol + else: + mixvol = vol + else: + if num_cols > 1: + if i == 0: + mixvol = 2 * vol + else: + mixvol = vol + else: + mixvol = vol + tiptrack(m1000, tips) + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + source = binding_buffer[i // 3] + if i == 0 or i == 3: + reps = 6 + else: + reps = 1 + bead_mixing(source, m1000, mixvol, reps=reps if not dry_run else 1) + ctx.delay(seconds=2) + m1000.blow_out(source.top(-3)) + # Transfer beads and binding from source to H-S plate + m1000.flow_rate.dispense = 80 + for t in range(num_trans): + m1000.transfer(vol_per_trans, source.bottom(1), well.top(), air_gap=20, new_tip="never") + m1000.flow_rate.dispense = 300 + bead_mixing(well, m1000, vol_per_trans, reps=6 if not dry_run else 1) + m1000.drop_tip() + + ctx.comment("-----Mixing Bind and Lysis-----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1800) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Shake at 1800 rpm for 5 minutes.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 1800 rpm for 5 minutes.") + + # Transfer from H-S plate to Magdeck plate + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for bindi in np.arange(settling_time + 1, 0, -0.5): # Settling time delay with countdown timer + ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") + + # remove initial supernatant + remove_supernatant(vol + starting_vol) + # Move Plate From Magnet to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def wash(vol, source): + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + whichwash = int(1) + if source == wash2: + whichwash = int(2) + if source == wash3: + whichwash = int(3) + + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=0.3, msg="Please allow ~20 second dry shake to loosen the pellet") + h_s.deactivate_shaker() + + ctx.comment("-----Wash " + str(whichwash) + " is starting now------") + tiptrack(m1000, tips) + for i, m in enumerate(samples_m): + src = source[i // 2] + for n in range(num_trans): + m1000.transfer(vol_per_trans, src, m.top(), air_gap=10, new_tip="never") + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1900) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please allow 5 minutes of shaking to properly mix buffer in the beads") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 1900 rpm for 5 minutes.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for washi in np.arange(settling_time, 0, -0.5): # settling time timer for washes + ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in wash " + str(whichwash) + " incubation.") + + if whichwash != 3: + remove_supernatant(vol) + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def lastwash(vol, source): + global whichwash + global tip1k + whichwash = 4 + ctx.comment("-----Wash " + str(whichwash) + " is starting now------") + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + for x, well in enumerate(samples_m): + tiptrack(m1000, tips_sn) + m1000.flow_rate.aspirate = 300 + m1000.aspirate(wash3_vol, well) + m1000.dispense(wash3_vol, waste) + m1000.return_tip() + tiptrack(m1000, tips) + m1000.flow_rate.dispense = 30 + m1000.aspirate(vol, source[x]) + m1000.dispense(vol, well) + ctx.delay(seconds=10) + m1000.flow_rate.aspirate = 50 + m1000.flow_rate.dispense = 150 + m1000.aspirate(vol, well) + m1000.dispense(vol, waste) + m1000.drop_tip() + m1000.pick_up_tip(tips[tip1k]) + m1000.aspirate(elution_vol, elution_solution) + m1000.dispense(elution_vol, well) + m1000.return_tip() + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def elute(vol): + ctx.comment("-----Beginning Elution Steps-----") + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Shake on H-S for 5 minutes at 2000 rpm.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 5 minutes.") + + # Transfer back to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for elutei in np.arange(settling_time, 0, -0.5): + ctx.delay(minutes=0.5, msg="Incubating on MagDeck for " + str(elutei) + " more minutes.") + + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + tiptrack(m1000, tips) + m1000.flow_rate.dispense = 100 + m1000.flow_rate.aspirate = 10 + m1000.transfer(vol, m.bottom(0.15), e.bottom(5), air_gap=20, new_tip="never") + m1000.blow_out(e.top(-2)) + m1000.air_gap(20) + m1000.flow_rate.aspirate = 300 + m1000.drop_tip() + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + if inc_lysis: + lysis(lysis_total_vol, lysis_) + bind(binding_buffer_vol) + wash(wash1_vol, wash1) + if not dry_run: + wash(wash2_vol, wash2) + wash(wash3_vol, wash3) + lastwash(wash4_vol, wash4) + elute(elution_vol) diff --git a/app-testing/files/protocols/pl_MagMax_RNA_Cells_Flex_96_Channel.py b/app-testing/files/protocols/pl_MagMax_RNA_Cells_Flex_96_Channel.py new file mode 100644 index 00000000000..28871125c09 --- /dev/null +++ b/app-testing/files/protocols/pl_MagMax_RNA_Cells_Flex_96_Channel.py @@ -0,0 +1,557 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"dry_run":false,"temp_mod":true,"heater_shaker":true,"tip_mixing":false,"wash_vol":150,"lysis_vol":140,"sample_vol":10,"stop_vol":100,"dnase_vol":50,"elution_vol":50,"protocol_filename":"MagMax_RNA_Cells-Flex_96_Channel"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +from opentrons import protocol_api +import json +import math +from opentrons import types +import numpy as np + +metadata = {"author": "Zach Galluzzo "} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + +whichwash = 1 + + +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + trash_chute = False # If false, waste bin loaded in D3, if True, trash chute loaded there + USE_GRIPPER = True + dry_run = False + temp_mod = True + heater_shaker = True + tip_mixing = False + + wash_vol = 150 + sample_vol = 10 + dnase_vol = 50 + elution_vol = 50 + lysis_vol = 140 + stop_vol = 100 + + try: + [ + trash_chute, + USE_GRIPPER, + dry_run, + temp_mod, + heater_shaker, + tip_mixing, + wash_vol, + sample_vol, + lysis_vol, + dnase_vol, + stop_vol, + elution_vol, + ] = get_values( # noqa: F821 + "trash_chute", + "USE_GRIPPER", + "dry_run", + "temp_mod", + "heater_shaker", + "tip_mixing", + "wash_vol", + "sample_vol", + "lysis_vol", + "dnase_vol", + "stop_vol", + "elution_vol", + ) + + except NameError: + pass + + # Just to be safe + if heater_shaker: + tip_mixing = False + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + if not dry_run: + settling_time = 2 + else: + settling_time = 0.25 + bead_vol = 20 + starting_vol = sample_vol + lysis_vol + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") # Plate with just beads + + h_s.close_labware_latch() + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Sample Plate") + + samples_m = sample_plate.wells()[0] + + beads = ctx.define_liquid(name="Beads", description="Magnetic Beads", display_color="#A52A2A") + for well in sample_plate.wells(): + well.load_liquid(liquid=beads, volume=bead_vol) + + if temp_mod: + tempdeck = ctx.load_module("Temperature Module Gen2", "A3") + tempblock = tempdeck.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = tempblock.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate") + + magblock = ctx.load_module("magneticBlockV1", "C1") + cell_plate = magblock.load_labware(deepwell_type, "Cell Plate") # Cell pellets + cells_m = cell_plate.wells()[0] + + if not dry_run: + tempdeck.set_temperature(4) + + #'#008000','#A52A2A','#00FFFF','#0000FF','#800080','#ADD8E6','#FF0000','#FFFF00','#FF00FF','#00008B','#7FFFD4','#FFC0CB','#FFA500','#00FF00','#C0C0C0' + + # Defining Liquid Reservoirs and Assigning Colors/ Locations + + lysis_reservoir = ctx.load_labware(deepwell_type, "C2", "Lysis reservoir") + lysis_res = lysis_reservoir.wells()[0] + lysis_buffer = ctx.define_liquid(name="Lysis Buffer", description="Lysis Buffer", display_color="#008000") + for well in lysis_reservoir.wells(): + well.load_liquid(liquid=lysis_buffer, volume=lysis_vol + 100) + + wash1_reservoir = ctx.load_labware(deepwell_type, "C3", "Wash 1 reservoir") + wash1 = wash1_reservoir.wells()[0] + wash1_buffer = ctx.define_liquid(name="Wash 1 Buffer", description="Wash 1 Buffer", display_color="#00008B") + for well in wash1_reservoir.wells(): + well.load_liquid(liquid=wash1_buffer, volume=wash_vol + 100) + + wash2_res = ctx.load_labware(deepwell_type, "B3", "Washes 2-4 reservoir") + wash2 = wash3 = wash4 = wash2_res.wells()[0] + wash2_buffer = ctx.define_liquid(name="Washes 2-4 Buffer", description="Washes 2-4 Buffer", display_color="#00FFFF") + for well in wash2_res.wells(): + well.load_liquid(liquid=wash2_buffer, volume=(3 * wash_vol) + 100) + + wash5_plate = ctx.load_labware(deepwell_type, "D2", "Washes 5 reservoir") + wash5 = wash5_plate.wells()[0] + wash5_buffer = ctx.define_liquid(name="Wash 5 Buffer", description="Wash 5 Buffer", display_color="#0000FF") + for well in wash5_plate.wells(): + well.load_liquid(liquid=wash5_buffer, volume=wash_vol + 100) + + dnase_reservoir = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "B1", "DNAse Reservoir") + dnase_res = dnase_reservoir.wells()[0] + dnase_buffer = ctx.define_liquid(name="DNAseI Buffer", description="DNAseI Buffer", display_color="#800080") + for well in dnase_reservoir.wells(): + well.load_liquid(liquid=dnase_buffer, volume=dnase_vol + 3) + + stop_reservoir = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "B2", "Stop Reservoir") + stop_res = stop_reservoir.wells()[0] + stop_buffer = ctx.define_liquid(name="Stop Buffer", description="Stop Buffer", display_color="#ADD8E6") + for well in stop_reservoir.wells(): + well.load_liquid(liquid=stop_buffer, volume=stop_vol + 10) + + elution_res = elutionplate.wells()[0] + elution_buffer = ctx.define_liquid(name="Elution Buffer", description="Elution Buffer", display_color="#FF00FF") + for well in elutionplate.wells(): + well.load_liquid(liquid=elution_buffer, volume=elution_vol + 5) + + # Load tips + tips = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A1", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + tips1 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A2", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + + # load 96 channel pipette + pip = ctx.load_instrument("flex_96channel_1000", mount="left") + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + pip.flow_rate.blow_out = 300 + + def remove_supernatant(vol, waste): + pip.pick_up_tip(tips) + if vol > 1000: + x = 2 + else: + x = 1 + transfer_vol = vol + for i in range(x): + pip.aspirate(transfer_vol, samples_m.bottom(0.15)) + pip.dispense(transfer_vol, waste) + pip.return_tip() + + # Transfer plate from magnet to H/S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def resuspend_pellet(vol, plate, reps=3): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=1, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0.75, y=0.75, z=1)) + loc3 = plate.bottom().move(types.Point(x=0, y=1, z=1)) + loc4 = plate.bottom().move(types.Point(x=-0.75, y=0.75, z=1)) + loc5 = plate.bottom().move(types.Point(x=-1, y=0, z=1)) + loc6 = plate.bottom().move(types.Point(x=-0.75, y=0 - 0.75, z=1)) + loc7 = plate.bottom().move(types.Point(x=0, y=-1, z=1)) + loc8 = plate.bottom().move(types.Point(x=0.75, y=-0.75, z=1)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc2) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc3) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc4) + pip.dispense(mixvol, loc4) + pip.aspirate(mixvol, loc5) + pip.dispense(mixvol, loc5) + pip.aspirate(mixvol, loc6) + pip.dispense(mixvol, loc6) + pip.aspirate(mixvol, loc7) + pip.dispense(mixvol, loc7) + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + def bead_mix(vol, plate, reps=5): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=0, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0, y=0, z=8)) + loc3 = plate.bottom().move(types.Point(x=0, y=0, z=16)) + loc4 = plate.bottom().move(types.Point(x=0, y=0, z=24)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc4) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + def mixing(well, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(1.5) + disp = well.bottom(20) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 100 + pip.flow_rate.dispense = 50 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def lysis(vol, source): + ctx.comment("--------Beginning Lysis--------") + pip.pick_up_tip(tips) + pip.flow_rate.aspirate = 80 + pip.aspirate(vol, source) + pip.dispense(vol, cells_m) + pip.flow_rate.aspirate = 300 + mixing(cells_m, vol, reps=12 if not dry_run else 1) + + def bind(): + """ + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead bining. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + ctx.comment("--------Beginning Bind Transfer--------") + # Transfer cells+lysis/bind to wells with beads + pip.aspirate(175, cells_m) + pip.air_gap(10) + pip.dispense(185, samples_m) + bead_mix(140, samples_m, reps=5 if not dry_run else 1) + pip.air_gap(10) + if not tip_mixing: + pip.return_tip() + + # Incubate for beads to bind DNA + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + + # Move empty cell plate off deck + ctx.move_labware(cell_plate, "C4" if USE_GRIPPER else protocol_api.OFF_DECK, use_gripper=False) + + ctx.delay(minutes=4.5 if not dry_run else 0.25, msg="Please wait 5 minutes while the sample binds with the beads.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(140, samples_m, reps=13) + pip.return_tip() + + # Move empty cell plate off deck + ctx.move_labware(cell_plate, "C4" if USE_GRIPPER else protocol_api.OFF_DECK, use_gripper=False) + + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 2000 rpm.") + # Move empty cell plate off deck + ctx.move_labware(cell_plate, "C4" if USE_GRIPPER else protocol_api.OFF_DECK, use_gripper=False) + + # Transfer plate to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for bindi in np.arange(settling_time + 2 if not dry_run else settling_time, 0, -0.5): # Settling time delay with countdown timer + ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") + + # remove initial supernatant + remove_supernatant(175, lysis_res) + + def wash(vol, source, waste): + + global whichwash # Defines which wash the protocol is on to log on the app + + ctx.comment("--------Beginning Wash #" + str(whichwash) + "--------") + + pip.pick_up_tip(tips) + pip.aspirate(vol, source) + pip.dispense(vol, samples_m) + ctx.delay(seconds=1) + pip.blow_out(samples_m.top(-3)) + if not tip_mixing: + pip.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please allow 5 minutes for wash to mix on heater-shaker.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(vol, samples_m, reps=13) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 2000 rpm.") + + # Transfer plate to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for washi in np.arange(settling_time, 0, -0.5): # settling time timer for washes + ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in wash " + str(whichwash) + " incubation.") + + remove_supernatant(vol, lysis_res) + + whichwash = whichwash + 1 + + def dnase(vol, source): + ctx.comment("--------Beginning DNAseI Step--------") + pip.flow_rate.aspirate = 20 + pip.flow_rate.dispense = 50 + + pip.pick_up_tip(tips) + pip.aspirate(vol, source) + pip.dispense(vol, samples_m) + resuspend_pellet(45, samples_m, reps=4 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please wait 10 minutes while the dnase incubates.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + for x in range(2): + bead_mix(vol, samples_m, reps=10) + ctx.delay(minutes=2) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 10 minutes.") + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + def stop_reaction(vol, source): + ctx.comment("--------Beginning Stop Reaction--------") + + pip.pick_up_tip(tips) + pip.aspirate(vol, source) + pip.dispense(vol, samples_m) + mixing(samples_m, vol, reps=2 if not dry_run else 1) + pip.blow_out(samples_m.top(-3)) + pip.air_gap(10) + if not tip_mixing: + pip.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2200) + ctx.delay(minutes=3 if not dry_run else 0.25, msg="Please wait 3 minutes while the stop solution inactivates the dnase.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(vol, samples_m, reps=10) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 3 minutes at 2200 rpm.") + + # Transfer plate to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for stop in np.arange(settling_time + 2, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(stop) + " minutes left in this incubation.") + + remove_supernatant(vol + dnase_vol, lysis_res) + + def elute(vol, source): + ctx.comment("--------Beginning Elution--------") + pip.pick_up_tip(tips1) + # Transfer + pip.aspirate(vol, source) + pip.dispense(vol, samples_m) + # Mix + for r in range(10 if not dry_run else 1): + pip.aspirate(40, samples_m.bottom(1.5)) + pip.dispense(40, samples_m.bottom(5)) + if r == 9: + pip.flow_rate.dispense = 20 + pip.aspirate(40, samples_m) + pip.dispense(40, samples_m.bottom(5)) + pip.flow_rate.dispense = 300 + if not tip_mixing: + pip.return_tip() + + # Elution Incubation + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay(minutes=3 if not dry_run else 0.25, msg="Please allow 3 minutes for elution buffer to elute RNA from beads.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(vol, samples_m, reps=10) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 3 minutes at 2000 rpm.") + + # Transfer plate to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for elutei in np.arange(settling_time + 2, 0, -0.5): + ctx.delay(minutes=0.5, msg="Incubating on MagDeck for " + str(elutei) + " more minutes.") + + pip.flow_rate.aspirate = 25 + pip.flow_rate.dispense = 25 + + # Transfer From Sample Plate to Elution Plate + pip.pick_up_tip(tips1) + pip.aspirate(vol, samples_m) + pip.dispense(vol, source) + pip.return_tip() + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + # Start Protocol + lysis(lysis_vol, lysis_res) + bind() + wash(wash_vol, wash1, lysis_res) + if not dry_run: + wash(wash_vol, wash2, lysis_res) + # dnase1 treatment + dnase(dnase_vol, dnase_res) + stop_reaction(stop_vol, stop_res) + # Resume washes + if not dry_run: + wash(wash_vol, wash3, lysis_res) + wash(wash_vol, wash4, lysis_res) + wash(wash_vol, wash5, lysis_res) + drybeads = 2 if not dry_run else 0.5 # Number of minutes you want to dry for + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + elute(elution_vol, elution_res) diff --git a/app-testing/files/protocols/pl_MagMax_RNA_Cells_Flex_multi.py b/app-testing/files/protocols/pl_MagMax_RNA_Cells_Flex_multi.py new file mode 100644 index 00000000000..138309ba272 --- /dev/null +++ b/app-testing/files/protocols/pl_MagMax_RNA_Cells_Flex_multi.py @@ -0,0 +1,671 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"dry_run":false,"mount":"left","inc_lysis":true,"temp_mod":true,"heater_shaker":true,"res_type":"nest_12_reservoir_15ml","num_samples":8,"wash_vol":150,"lysis_vol":140,"sample_vol":50,"stop_vol":100,"dnase_vol":50,"elution_vol":50,"protocol_filename":"MagMax_RNA_Cells-Flex_multi"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +import json +import math +from opentrons import types +import numpy as np + +metadata = { + "author": "Zach Galluzzo ", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + +""" +Here is where you can modify the magnetic module engage height: +""" +whichwash = 1 +sample_max = 48 +tip = 0 +drop_count = 0 +waste_vol = 0 + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + trash_chute = False + USE_GRIPPER = True + dry_run = False + inc_lysis = True + mount = "left" + res_type = "nest_12_reservoir_15ml" + temp_mod = True + heater_shaker = True + + num_samples = 48 + wash_vol = 150 + sample_vol = 50 + lysis_vol = 140 + stop_vol = 100 + elution_vol = 50 + dnase_vol = 50 + + try: + [ + res_type, + temp_mod, + trash_chute, + USE_GRIPPER, + dry_run, + inc_lysis, + mount, + num_samples, + heater_shaker, + wash_vol, + lysis_vol, + sample_vol, + stop_vol, + dnase_vol, + elution_vol, + ] = get_values( # noqa: F821 + "res_type", + "temp_mod", + "trash_chute", + "USE_GRIPPER", + "dry_run", + "inc_lysis", + "mount", + "num_samples", + "heater_shaker", + "wash_vol", + "lysis_vol", + "sample_vol", + "stop_vol", + "dnase_vol", + "elution_vol", + ) + + except NameError: + pass + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + + if not dry_run: + settling_time = 2 + else: + settling_time = 0.25 + bead_vol = 20 + starting_vol = sample_vol + lysis_vol + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") + h_s.close_labware_latch() + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Sample Plate") + + if temp_mod: + temp = ctx.load_module("temperature module gen2", "A3") + temp_block = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = temp_block.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate") + if not dry_run: + temp.set_temperature(4) + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate") + + magblock = ctx.load_module("magneticBlockV1", "C1") + waste = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste").wells()[0].top() + res1 = ctx.load_labware(res_type, "D2", "reagent reservoir 1") + num_cols = math.ceil(num_samples / 8) + + # Load tips and combine all similar boxes + tips200 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A1", "Tips 1") + tips201 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A2", "Tips 2") + tips202 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B1", "Tips 3") + tips203 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B2", "Tips 4") + tips = [*tips200.wells()[8 * (num_cols) : 96], *tips201.wells(), *tips202.wells(), *tips203.wells()] + tips_sn = tips200.wells()[: 8 * (num_cols)] + + # load P1000M pipette + m1000 = ctx.load_instrument("flex_8channel_1000", mount) + + # Load Liquid Locations in Reservoir + elution_solution = elutionplate.rows()[0][:num_cols] + dnase1 = elutionplate.rows()[0][num_cols : 2 * num_cols] + lysis_ = res1.wells()[0] + stopreaction = res1.wells()[1] + wash1 = res1.wells()[2] + wash2 = res1.wells()[3] + wash3 = res1.wells()[4] + wash4 = res1.wells()[5] + wash5 = res1.wells()[6] + + """ + Here is where you can define the locations of your reagents. + """ + samples_m = sample_plate.rows()[0][:num_cols] # 20ul beads each well + cells_m = sample_plate.rows()[0][num_cols : 2 * num_cols] + elution_samples_m = elutionplate.rows()[0][:num_cols] + # Do the same for color mapping + beads_ = sample_plate.wells()[: (8 * num_cols)] + cells_ = sample_plate.wells()[(8 * num_cols) : (16 * num_cols)] + elution_samps = elutionplate.wells()[: (8 * num_cols)] + dnase1_ = elutionplate.wells()[(8 * num_cols) : (16 * num_cols)] + + colors = [ + "#008000", + "#A52A2A", + "#00FFFF", + "#0000FF", + "#800080", + "#ADD8E6", + "#FF0000", + "#FFFF00", + "#FF00FF", + "#00008B", + "#7FFFD4", + "#FFC0CB", + "#FFA500", + "#00FF00", + "#C0C0C0", + ] + + locations = [lysis_, wash1, wash2, wash3, wash4, wash5, stopreaction] + vols = [lysis_vol, wash_vol, wash_vol, wash_vol, wash_vol, wash_vol, stop_vol] + liquids = ["Lysis", "Wash 1", "Wash 2", "Wash 3", "Wash 4", "Wash 5", "Stop"] + + dnase = ctx.define_liquid(name="DNAse", description="DNAse", display_color="#C0C0C0") + eluate = ctx.define_liquid(name="Elution Buffer", description="Elution Buffer", display_color="#00FF00") + bead = ctx.define_liquid(name="Beads", description="Beads", display_color="#FFA500") + sample = ctx.define_liquid(name="Sample", description="Cell Pellet", display_color="#FFC0CB") + + # Add liquids to non-reservoir labware + for i in beads_: + i.load_liquid(liquid=bead, volume=bead_vol) + for i in cells_: + i.load_liquid(liquid=sample, volume=0) + for i in dnase1_: + i.load_liquid(liquid=dnase, volume=dnase_vol) + for i in elution_samps: + i.load_liquid(liquid=eluate, volume=elution_vol) + + delete = len(colors) - len(liquids) + + if delete >= 1: + for i in range(delete): + colors.pop(-1) + + def liquids_(liq, location, color, vol): + sampnum = 8 * (math.ceil(num_samples / 8)) + """ + Takes an individual liquid at a time and adds the color to the well + in the description. + """ + # Volume Calculation + extra_samples = math.ceil(1500 / vol) + + # Defining and assigning liquids to wells + # if isinstance(location,list): + # limit = sample_max/len(location) #Calculates samples/ res well + # iterations = math.ceil(sampnum/limit) + # left = sampnum - limit + # while left>limit: + # left = left - limit + # if left > 0: + # last_iteration_samp_num = left + # elif left < 0: + # last_iteration_samp_num = sampnum + # else: + # last_iteration_samp_num = limit + + # samples_per_well = [] + + # for i in range(iterations): + # #append the left over on the last iteration + # if i == (iterations-1): + # samples_per_well.append(last_iteration_samp_num) + # else: + # samples_per_well.append(limit) + + # liq = ctx.define_liquid(name=str(liq),description=str(liq),display_color=color) + # for sample, well in zip(samples_per_well,location[:len(samples_per_well)]): + # v = vol*(sample+extra_samples) + # well.load_liquid(liquid=liq,volume=v) + # else: + v = vol * (sampnum + extra_samples) + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + location.load_liquid(liquid=liq, volume=v) + + for x, (ll, l, c, v) in enumerate(zip(liquids, locations, colors, vols)): + liquids_(ll, l, c, v) + + m1000.flow_rate.aspirate = 50 + m1000.flow_rate.dispense = 150 + m1000.flow_rate.blow_out = 300 + + def tiptrack(pip, tipbox): + global tip + global drop_count + pip.pick_up_tip(tipbox[int(tip)]) + tip = tip + 8 + drop_count = drop_count + 8 + if drop_count >= 250: + drop_count = 0 + ctx.pause("Please empty the waste bin of all the tips before continuing.") + + def blink(): + for i in range(3): + ctx.set_rail_lights(True) + ctx.delay(minutes=0.01666667) + ctx.set_rail_lights(False) + ctx.delay(minutes=0.01666667) + + def remove_supernatant(vol): + ctx.comment("-----Removing Supernatant-----") + m1000.flow_rate.aspirate = 30 + num_trans = math.ceil(vol / 180) + vol_per_trans = vol / num_trans + + def _waste_track(vol): + global waste_vol + waste_vol = waste_vol + (vol * 8) + if waste_vol >= 185000: + m1000.home() + blink() + ctx.pause("Please empty liquid waste before resuming.") + waste_vol = 0 + + for i, m in enumerate(samples_m): + m1000.pick_up_tip(tips_sn[8 * i]) + loc = m.bottom(0.5) + for _ in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, m.top()) + m1000.move_to(m.center()) + m1000.transfer(vol_per_trans, loc, waste, new_tip="never", air_gap=20) + m1000.blow_out(waste) + m1000.air_gap(20) + m1000.drop_tip(tips_sn[8 * i]) + m1000.flow_rate.aspirate = 300 + # Move Plate From Magnet to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def bead_mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top().move(types.Point(x=0, y=0, z=5)) + aspbot = well.bottom().move(types.Point(x=0, y=0, z=1)) + asptop = well.bottom().move(types.Point(x=2, y=-2, z=1)) + disbot = well.bottom().move(types.Point(x=-2, y=1.5, z=2)) + distop = well.bottom().move(types.Point(x=0, y=0, z=6)) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, aspbot) + pip.dispense(vol, distop) + pip.aspirate(vol, asptop) + pip.dispense(vol, disbot) + if _ == reps - 1: + pip.flow_rate.aspirate = 100 + pip.flow_rate.dispense = 75 + pip.aspirate(vol, aspbot) + pip.dispense(vol, aspbot) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(0.5) + disp = well.top(-8) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 100 + pip.flow_rate.dispense = 75 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def lysis(vol, source): + ctx.comment("-----Beginning lysis steps-----") + num_transfers = math.ceil(vol / 180) + tiptrack(m1000, tips) + for i in range(num_cols): + src = source + tvol = vol / num_transfers + for t in range(num_transfers): + m1000.aspirate(tvol, src.bottom(1)) + m1000.dispense(m1000.current_volume, cells_m[i].top(-3)) + + # mix after adding all reagent to wells with cells + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + for x in range(8 if not dry_run else 1): + m1000.aspirate(tvol * 0.75, cells_m[i].bottom(0.5)) + m1000.dispense(tvol * 0.75, cells_m[i].bottom(8)) + if x == 3: + ctx.delay(minutes=0.0167) + m1000.blow_out(cells_m[i].bottom(1)) + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2200) + ctx.delay(minutes=1 if not dry_run else 0.25, msg="Please allow 1 minute incubation for cells to lyse") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2200 rpm for 1 minute.") + + def bind(): + """ + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead binding. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + ctx.comment("-----Beginning bind steps-----") + for i, well in enumerate(samples_m): + # Transfer cells+lysis/bind to wells with beads + tiptrack(m1000, tips) + m1000.aspirate(185, cells_m[i].bottom(0.1)) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, well.bottom(8)) + # Mix after transfer + bead_mixing(well, m1000, 130, reps=5 if not dry_run else 1) + m1000.air_gap(10) + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please allow 5 minute incubation for beads to bind to DNA") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 5 minutes.") + + # Transfer from H-S plate to Magdeck plate + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for bindi in np.arange(settling_time, 0, -0.5): # Settling time delay with countdown timer + ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") + + # remove initial supernatant + remove_supernatant(180) + + def wash(vol, source): + + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + whichwash = 1 + if source == wash2: + whichwash = 2 + if source == wash3: + whichwash = 3 + if source == wash4: + whichwash = 4 + + ctx.comment("-----Now starting Wash #" + str(whichwash) + "-----") + + tiptrack(m1000, tips) + num_trans = math.ceil(vol / 180) + vol_per_trans = vol / num_trans + for i, m in enumerate(samples_m): + src = source + for n in range(num_trans): + m1000.aspirate(vol_per_trans, src) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, m.top(-2)) + ctx.delay(seconds=2) + m1000.blow_out(m.top(-2)) + m1000.air_gap(10) + m1000.drop_tip() + + # Shake for 5 minutes to mix wash with beads + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please allow 5 minute incubation for beads to mix in wash buffer") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 5 minutes.") + + # Transfer from H-S plate to Magdeck plate + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for washi in np.arange(settling_time, 0, -0.5): # settling time timer for washes + ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in wash " + str(whichwash) + " incubation.") + + remove_supernatant(vol) + + def dnase(vol, source): + ctx.comment("-----DNAseI Steps Beginning-----") + num_trans = math.ceil(vol / 180) + vol_per_trans = vol / num_trans + tiptrack(m1000, tips) + for i, m in enumerate(samples_m): + src = source[i] + m1000.flow_rate.aspirate = 10 + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.aspirate(vol_per_trans, src.bottom(0.15)) + m1000.dispense(vol_per_trans, m.top(-3)) + m1000.blow_out(m.top(-3)) + m1000.air_gap(20) + + m1000.flow_rate.aspirate = 300 + + # Is this mixing needed? \/\/\/ + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + mixing(samples_m[i], m1000, 45, reps=5 if not dry_run else 1) + m1000.drop_tip() + + # Shake for 10 minutes to mix DNAseI + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please allow 10 minute incubation for DNAse1 to work") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 10 minutes.") + + def stop_reaction(vol, source): + ctx.comment("-----Adding Stop Solution-----") + tiptrack(m1000, tips) + num_trans = math.ceil(vol / 180) + vol_per_trans = vol / num_trans + for i, m in enumerate(samples_m): + src = source + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.transfer(vol_per_trans, src, m.top(), air_gap=20, new_tip="never") + m1000.blow_out(m.top(-3)) + m1000.air_gap(20) + + m1000.drop_tip() + + # Shake for 3 minutes to mix wash with beads + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=3 if not dry_run else 0.25, msg="Please allow 3 minute incubation to inactivate DNAse1") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 3 minutes.") + + # Transfer from H-S plate to Magdeck plate + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for stop in np.arange(settling_time, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(stop) + " minutes left in this incubation.") + + remove_supernatant(vol + 50) + + def elute(vol): + ctx.comment("-----Elution Beginning-----") + tiptrack(m1000, tips) + m1000.flow_rate.aspirate = 10 + for i, m in enumerate(samples_m): + loc = m.top(-2) + m1000.aspirate(vol, elution_solution[i]) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, loc) + m1000.blow_out(m.top(-3)) + m1000.air_gap(10) + + m1000.flow_rate.aspirate = 300 + + # Is this mixing needed? \/\/\/ + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + for mixes in range(10): + m1000.aspirate(elution_vol - 10, samples_m[i]) + m1000.dispense(elution_vol - 10, samples_m[i].bottom(10)) + if mixes == 9: + m1000.flow_rate.dispense = 20 + m1000.aspirate(elution_vol - 10, samples_m[i]) + m1000.dispense(elution_vol - 10, samples_m[i].bottom(10)) + m1000.flow_rate.dispense = 300 + m1000.drop_tip() + + # Shake for 3 minutes to mix wash with beads + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=3 if not dry_run else 0.25, msg="Please allow 3 minute incubation to elute RNA from beads") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 3 minutes.") + + # Transfer from H-S plate to Magdeck plate + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for elutei in np.arange(settling_time, 0, -0.5): + ctx.delay(minutes=0.5, msg="Incubating on MagDeck for " + str(elutei) + " more minutes.") + + ctx.comment("-----Trasnferring Sample to Elution Plate-----") + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + tiptrack(m1000, tips) + loc = m.bottom(0.1) + m1000.transfer(vol, loc, e.bottom(5), air_gap=20, new_tip="never") + m1000.blow_out(e.top(-2)) + m1000.air_gap(20) + m1000.drop_tip() + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + if inc_lysis: + lysis(lysis_vol, lysis_) + bind() + wash(wash_vol, wash1) + if not dry_run: + wash(wash_vol, wash2) + # dnase1 treatment + dnase(dnase_vol, dnase1) + stop_reaction(stop_vol, stopreaction) + # Resume washes + wash(wash_vol, wash3) + wash(wash_vol, wash4) + wash(wash_vol, wash5) + drybeads = 2 # Number of minutes you want to dry for + else: + drybeads = 0.25 + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + elute(elution_vol) diff --git a/app-testing/files/protocols/pl_Nanopore_Genomic_Ligation_v5_Final.py b/app-testing/files/protocols/pl_Nanopore_Genomic_Ligation_v5_Final.py new file mode 100644 index 00000000000..921a3e74268 --- /dev/null +++ b/app-testing/files/protocols/pl_Nanopore_Genomic_Ligation_v5_Final.py @@ -0,0 +1,988 @@ +from opentrons import protocol_api +from opentrons import types +import math +import numpy as np + +metadata = { + "protocolName": "Nanopore Genomic Ligation 24x v5", + "author": "Opentrons ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.18", +} + + +# PROTOCOL BLOCKS +STEP_ERAT = 1 +STEP_ERATDECK = 1 +STEP_POSTERAT = 1 +STEP_LIG = 1 +STEP_LIGDECK = 1 +STEP_POSTLIG = 1 +STEP_ELUTDECK = 1 + +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ + +p200_tips = 0 +p50_tips = 0 +p200_tipracks_count = 0 +p50_tipracks_count = 0 +WasteVol = 0 +Resetcount = 0 + + +def add_parameters(parameters): + + parameters.add_bool( + variable_name="DRYRUN", display_name="Dry Run", description="Skip incubation delays and shorten mix steps.", default=False + ) + + parameters.add_bool(variable_name="TIP_TRASH", display_name="Trash tip", description="tip thrases after every use", default=True) + + parameters.add_bool( + variable_name="DEACTIVATE_TEMP", + display_name="deactivate temperature", + description="thermocycler temperature deactivates if running from start", + default=True, + ) + + parameters.add_int( + variable_name="samples", + display_name="number of samples", + description="How many samples to be perform for library prep", + default=8, + minimum=8, + maximum=48, + ) + + parameters.add_int( + variable_name="COLUMNS", + display_name="number of columns", + description="How many column to be perform for library prep", + default=1, + minimum=1, + maximum=3, + ) + + parameters.add_int( + variable_name="ERVOL", + display_name="End Repair Volume", + description="Input End repair Volume", + default=13, + minimum=13, + maximum=30, + ) + + parameters.add_int( + variable_name="ADAPVOL", + display_name="Adapter Ligation Volume", + description="Fragmentation time in thermocycler", + default=40, + minimum=40, + maximum=80, + ) + + parameters.add_str( + display_name="Resevoir Type", + variable_name="RES_TYPE", + choices=[ + {"display_name": "nest_12_reservoir_15ml", "value": "12x15ml"}, + {"display_name": "nest_96_wellplate_2ml_deep", "value": "96x2ml"}, + ], + default="96x2ml", + description="Select Resevoir type for Room temperature Reagents", + ) + + +def run(protocol: protocol_api.ProtocolContext): + + # SCRIPT SETTINGS + DRYRUN = protocol.params.DRYRUN # True = skip incubation times, shorten mix, for testing purposes + USE_GRIPPER = True # True = Uses Gripper, False = Manual Move + TIP_TRASH = protocol.params.TIP_TRASH # True = Used tips go in Trash, False = Used tips go back into rack + DEACTIVATE_TEMP = ( + protocol.params.DEACTIVATE_TEMP + ) # True = Deactivates Temp Block and Thermocycler, False = Leaves Temp Block and Thermocycler on (if leaving finished plates on deck) + + # PROTOCOL SETTINGS + COLUMNS = protocol.params.COLUMNS # 1-3 + samples = protocol.params.samples + ERVOL = protocol.params.ERVOL + ADAPVOL = protocol.params.ADAPVOL + # TIP SAVING SETTINGS + RES_TYPE = protocol.params.RES_TYPE # '12x15ml' or '96x2ml' + AirMultiDispense = False + REuse_TIPS = False + + ABR_TEST = False + if ABR_TEST == True: + COLUMNS = 3 + DRYRUN = False # Overrides to only DRYRUN + TIP_TRASH = True # Overrides to only REUSING TIPS + RUN = 3 # Repetitions + else: + RUN = 1 + + global p200_tips + global p50_tips + global p200_tipracks_count + global p50_tipracks_count + global WasteVol + global Resetcount + + if ABR_TEST == True: + protocol.comment("THIS IS A ABR RUN WITH " + str(RUN) + " REPEATS") + protocol.comment("THIS IS A DRY RUN") if DRYRUN == True else protocol.comment("THIS IS A REACTION RUN") + protocol.comment("USED TIPS WILL GO IN TRASH") if TIP_TRASH == True else protocol.comment("USED TIPS WILL BE RE-RACKED") + + # DECK SETUP AND LABWARE + # ========== FIRST ROW =========== + heatershaker = protocol.load_module("heaterShakerModuleV1", "D1") + temp_block = protocol.load_module("temperature module gen2", "C1") + reagent_plate = temp_block.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") + if RES_TYPE == "12x15ml": + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "C2", "Reservoir") + if RES_TYPE == "96x2ml": + reservoir = protocol.load_labware("nest_96_wellplate_2ml_deep", "C2", "Reservoir") + # ========== SECOND ROW ========== + MAG_PLATE_SLOT = protocol.load_module(module_name="magneticBlockV1", location="D2") + tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "B2") + tiprack_50_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "C3") + # ========== THIRD ROW =========== + thermocycler = protocol.load_module("thermocycler module gen2") + sample_plate_1 = thermocycler.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") + tiprack_200_2 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "A2") + tiprack_200_4 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "B3") + # ========== FOURTH ROW ========== + tiprack_200_3 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "A3") + default_trash = protocol.load_trash_bin(location="D3") + + # ======== ESTIMATING LIQUIDS ======= + + # NAME = Number of columns x volume per reaction x 110% for overage = volume to be filled in each well (of 8 well column) + Sample_Volume = 40 + AMPure_Volume = COLUMNS * (180) * 1.1 + ETOH_Volume = COLUMNS * (900) * 1.1 + RSB_Volume = COLUMNS * (95) * 1.1 + LFB_Volume = COLUMNS * (95) * 1.1 + Elut_Volume = COLUMNS * (95) * 1.1 + ERAT_Volume = samples * (13) * 1.1 + LIG_Volume = samples * (13) * 1.1 + + TotalColumn = ["A", "B", "C", "D", "E", "F", "G", "H"] + UsedColumn = ["A", "B", "C", "D", "E", "F", "G", "H"] + + # ======== DEFINING LIQUIDS ======= + AMPure = protocol.define_liquid(name="AMPure", description="AMPure Beads", display_color="#704848") # 704848 = 'AMPure Brown' + EtOH = protocol.define_liquid(name="EtOH", description="80% Ethanol", display_color="#9ACECB") # 9ACECB = 'Ethanol Blue' + RSB = protocol.define_liquid(name="RSB", description="Resuspension Buffer", display_color="#00FFF2") # 00FFF2 = 'Base Light Blue' + LFB = protocol.define_liquid(name="LFB", description="LFB", display_color="#9B9B9B") + Elut = protocol.define_liquid(name="Elut", description="Elution", display_color="#52AAFF") + Liquid_trash_well = protocol.define_liquid( + name="Liquid_trash_well", description="Liquid Trash", display_color="#9B9B9B" + ) # 9B9B9B = 'Liquid Trash Grey' + Sample = protocol.define_liquid( + name="Sample", description="Sample", display_color="#52AAFF" + ) # 52AAFF = 'Sample Blue' #8400FF = 'Base Purple' + Final_Sample = protocol.define_liquid( + name="Final_Sample", description="Final Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + Placeholder_Sample = protocol.define_liquid( + name="Placeholder_Sample", description="Placeholder Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + ERAT = protocol.define_liquid(name="ERAT", description="End Repair", display_color="#FF0000") + LIG = protocol.define_liquid(name="LIG", description="Ligation", display_color="#FFA000") + # ======== LOADING LIQUIDS ======= + + # Filling Reservoirs + if RES_TYPE == "12x15ml": + reservoir.wells_by_name()["A1"].load_liquid(liquid=AMPure, volume=AMPure_Volume) + reservoir.wells_by_name()["A4"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()["A5"].load_liquid(liquid=RSB, volume=RSB_Volume) + reservoir.wells_by_name()["A6"].load_liquid(liquid=LFB, volume=LFB_Volume) + reservoir.wells_by_name()["A7"].load_liquid(liquid=Elut, volume=Elut_Volume) + reservoir.wells_by_name()["A12"].load_liquid(liquid=Liquid_trash_well, volume=0) + + if RES_TYPE == "96x2ml": + for loop, X in enumerate(UsedColumn): + reservoir.wells_by_name()[X + "1"].load_liquid(liquid=AMPure, volume=AMPure_Volume) + reservoir.wells_by_name()[X + "4"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()[X + "5"].load_liquid(liquid=RSB, volume=RSB_Volume) + reservoir.wells_by_name()[X + "6"].load_liquid(liquid=LFB, volume=LFB_Volume) + reservoir.wells_by_name()[X + "7"].load_liquid(liquid=Elut, volume=Elut_Volume) + reservoir.wells_by_name()[X + "12"].load_liquid(liquid=Liquid_trash_well, volume=0) + + # Filling Reagent plate + + for loop, X in enumerate(TotalColumn): + reagent_plate.wells_by_name()[X + "1"].load_liquid(liquid=ERAT, volume=ERAT_Volume) + reagent_plate.wells_by_name()[X + "2"].load_liquid(liquid=LIG, volume=LIG_Volume) + + # Filling sample and / or reagent wells, Sample count DEPENDENT + if COLUMNS >= 1: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "4"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Final_Sample, volume=0) + if COLUMNS >= 2: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Final_Sample, volume=0) + if COLUMNS >= 3: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "3"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "6"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Final_Sample, volume=0) + + # REAGENT PLATE + ERAT = reagent_plate.wells_by_name()["A1"] + LIG = reagent_plate.wells_by_name()["A2"] + + # RESERVOIR + AMPure = reservoir["A1"] + EtOH = reservoir["A4"] + RSB = reservoir["A5"] + LFB = reservoir["A6"] + Elut = reservoir["A7"] + Liquid_trash_well_1 = reservoir["A12"] + + # pipette + p1000 = protocol.load_instrument("flex_8channel_1000", "left", tip_racks=[tiprack_200_1, tiprack_200_2, tiprack_200_3, tiprack_200_4]) + p50 = protocol.load_instrument("flex_8channel_50", "right", tip_racks=[tiprack_50_1]) + + # SAMPLE TRACKING + + if COLUMNS == 1: + column_1_list = ["A1"] # sample_plate_1 initial Wells and Cleanup_1 + column_2_list = ["A4"] # sample_plate_1 Amplification and Cleanup_2 + column_3_list = ["A7"] # sample_plate_1 Final Libraries + barcodes = ["A7"] + if COLUMNS == 2: + column_1_list = ["A1", "A2"] # sample_plate_1 initial Wells and Cleanup_1 + column_2_list = ["A4", "A5"] # sample_plate_1 Amplification and Cleanup_2 + column_3_list = ["A7", "A8"] # sample_plate_1 Final Libraries + barcodes = ["A7", "A8"] + if COLUMNS == 3: + column_1_list = ["A1", "A2", "A3"] # sample_plate_1 initial Wells and Cleanup_1 + column_2_list = ["A4", "A5", "A6"] # sample_plate_1 Amplification and Cleanup_2 + column_3_list = ["A7", "A8", "A9"] # sample_plate_1 Final Libraries + barcodes = ["A7", "A8", "A9"] + if COLUMNS == 4: + column_1_list = ["A1", "A2", "A3", "A4"] # sample_plate_1 initial Wells and Cleanup_1 + column_2_list = ["A5", "A6", "A7", "A8"] # sample_plate_1 Amplification and Cleanup_2 + column_3_list = ["A9", "A10", "A11", "A12"] # sample_plate_1 Final Libraries + barcodes = ["A7", "A8", "A9", "A10"] + + # Pipette Flow Rate + p1000.flow_rate.aspirate = 150 + p1000.flow_rate.dispense = 150 + p1000.flow_rate.blow_out = 150 + p200_tipracks = 4 + p50_tipracks = 2 + + def tipcheck(tiptype): + global p200_tips + global p50_tips + global p200_tipracks_count + global p50_tipracks_count + global Resetcount + if tiptype == 200: + if p200_tips == p200_tipracks * 12: + if ABR_TEST == True: + p1000.reset_tipracks() + else: + protocol.pause("RESET p200 TIPS") + p1000.reset_tipracks() + Resetcount += 1 + p200_tipracks_count += 1 + p200_tips = 0 + if tiptype == 50: + if p50_tips == p50_tipracks * 12: + if ABR_TEST == True: + p50.reset_tipracks() + else: + protocol.pause("RESET p50 TIPS") + p50.reset_tipracks() + Resetcount += 1 + p50_tipracks_count += 1 + p50_tips = 0 + + Liquid_trash = Liquid_trash_well_1 + + def DispWasteVol(Vol): + global WasteVol + WasteVol += int(Vol) + if WasteVol < 1500: + Liquid_trash = Liquid_trash_well_1 + if WasteVol >= 1500 and WasteVol < 3000: + Liquid_trash = Liquid_trash_well_2 + if WasteVol >= 3000: + Liquid_trash = Liquid_trash_well_3 + + ############################################################################################################################################ + ############################################################################################################################################ + ############################################################################################################################################ + # commands + for loop in range(RUN): + thermocycler.open_lid() + heatershaker.open_labware_latch() + if DRYRUN == False: + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + thermocycler.set_block_temperature(6) + thermocycler.set_lid_temperature(100) + temp_block.set_temperature(6) + protocol.pause("Ready") + heatershaker.close_labware_latch() + Liquid_trash = Liquid_trash_well_1 + + # Sample Plate contains 100ng of DNA in 47ul Nuclease Free Water + + if STEP_ERAT == 1: + protocol.comment("==============================================") + protocol.comment("--> End Repair / A-Tailing") + protocol.comment("==============================================") + # Standard Setup + + protocol.comment("--> Adding FRERAT") + ERATVol = ERVOL + ERATMixRep = 10 if DRYRUN == False else 1 + ERATMixVol = 60 + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(ERATVol, ERAT.bottom(z=0.1)) + p1000.dispense(ERATVol, sample_plate_1[X].bottom(z=0.1)) + p1000.move_to(sample_plate_1[X].bottom(z=0.25)) + p1000.mix(ERATMixRep, ERATMixVol) + p1000.blow_out(sample_plate_1[X].top(z=-1)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + if STEP_ERATDECK == 1: + ############################################################################################################################################ + protocol.comment("Seal, Run ERAT (~10min)") + if DRYRUN == False: + thermocycler.close_lid() + profile_ERAT = [{"temperature": 20, "hold_time_minutes": 5}, {"temperature": 65, "hold_time_minutes": 5}] + thermocycler.execute_profile(steps=profile_ERAT, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(4) + ############################################################################################################################################ + thermocycler.open_lid() + protocol.pause("Remove Seal") + + if STEP_POSTERAT == 1: + protocol.comment("==============================================") + protocol.comment("--> Cleanup 1") + protocol.comment("==============================================") + # Standard Setup + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM THERMOCYCLER TO HEATERSHAKER + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=heatershaker, use_gripper=True) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> ADDING AMPure (0.8x)") + AMPureVol = 60 + SampleVol = 60 + AMPureMixRPM = 1800 + AirMultiDispense = False + AMPureMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 # Seconds + AMPurePremix = 3 if DRYRUN == False else 1 + # ======== DISPENSE =========== + if AirMultiDispense == True: + tipcheck(200) + p1000.pick_up_tip() + p1000.mix(AMPurePremix, 40, AMPure.bottom(z=1)) + for loop, X in enumerate(column_1_list): + p1000.aspirate(AMPureVol, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(AMPureVol, sample_plate_1[X].top(z=1), rate=1) + protocol.delay(seconds=0.2) + p1000.blow_out(sample_plate_1[X].top(z=-1)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + else: + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.mix(AMPurePremix, AMPureVol + 10, AMPure.bottom(z=1)) + p1000.aspirate(AMPureVol, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(AMPureVol, sample_plate_1[X].bottom(z=1), rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + for Mix in range(2): + p1000.aspirate(50, rate=0.5) + p1000.move_to(sample_plate_1[X].bottom(z=1)) + p1000.aspirate(30, rate=0.5) + p1000.dispense(30, rate=0.5) + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + p1000.dispense(50, rate=0.5) + Mix += 1 + p1000.blow_out(sample_plate_1[X].top(z=2)) + p1000.default_speed = 400 + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.move_to(sample_plate_1[X].top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + # ============================== + heatershaker.set_and_wait_for_shake_speed(rpm=AMPureMixRPM) + protocol.delay(AMPureMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATER SHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=MAG_PLATE_SLOT, use_gripper=True) + heatershaker.close_labware_latch() + + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_1[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].top(z=2)) + p1000.default_speed = 200 + p1000.dispense(200, Liquid_trash.top(z=0), rate=0.5) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=-2)) + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + AirMultiDispense = True + # ======== DISPENSE =========== + if AirMultiDispense == True: + p1000.pick_up_tip() + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.aspirate(ETOHMaxVol, EtOH.bottom(z=1)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(EtOH.top(z=-5)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=-1)) + p1000.dispense(ETOHMaxVol, rate=1) + protocol.delay(minutes=0.1) + p1000.blow_out(sample_plate_1[X].top(z=-1)) + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.move_to(sample_plate_1[X].top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + else: + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(ETOHMaxVol, EtOH.bottom(z=1)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(EtOH.top(z=-5)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=-2)) + p1000.dispense(ETOHMaxVol, rate=1) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.move_to(sample_plate_1[X].top(z=0)) + p1000.move_to(sample_plate_1[X].top(z=5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + # ============================== + if DRYRUN == False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_1[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].top(z=2)) + p1000.default_speed = 200 + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + if DRYRUN == False: + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual Wash") + for loop, X in enumerate(column_1_list): + tipcheck(50) + p50.pick_up_tip() # <---------------- Tip Pickup + p50.move_to(sample_plate_1[X].bottom(1)) + p50.aspirate(20, rate=0.25) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM MAG PLATE TO HEATER SHAKER + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=heatershaker, use_gripper=True) + heatershaker.close_labware_latch() + + # ============================================================================================ + + protocol.comment("--> Adding nuclease Free water") + RSBVol = 61 + RSBMixRPM = 2000 + AirMultiDispense = True + RSBMixRep = 5 * 60 if DRYRUN == False else 0.1 * 60 + # ======== DISPENSE =========== + if AirMultiDispense == True: + + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(RSBVol, RSB.bottom(z=1)) + p1000.move_to(sample_plate_1.wells_by_name()[X].top(z=3)) + p1000.dispense(RSBVol, rate=2) + p1000.blow_out(sample_plate_1.wells_by_name()[X].top(z=3)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + else: + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() # <---------------- Tip Pickup + p1000.aspirate(RSBVol, RSB.bottom(z=1)) + p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) + p1000.dispense(RSBVol, rate=1) + p1000.blow_out(sample_plate_1.wells_by_name()[X].top()) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + # =============================== + heatershaker.set_and_wait_for_shake_speed(rpm=RSBMixRPM) + protocol.delay(RSBMixRep) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATERSHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=MAG_PLATE_SLOT, use_gripper=True) + heatershaker.close_labware_latch() + + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Transferring Supernatant") + TransferSup = 60 + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=0.5)) + p1000.aspirate(TransferSup, rate=0.25) + p1000.dispense(TransferSup, sample_plate_1[column_2_list[loop]].bottom(z=1)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM MAG PLATE to Heatershaker + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=heatershaker, use_gripper=True) + heatershaker.close_labware_latch() + + # ============================================================================================ + + if STEP_LIG == 1: + protocol.comment("==============================================") + protocol.comment("--> Adapter Ligation") + protocol.comment("==============================================") + # Standard Setup + + protocol.comment("--> Adding Lig") + LIGVol = ADAPVOL + LIGMixRep = 20 if DRYRUN == False else 1 + LIGMixVol = 100 + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.mix(3, LIGVol, LIG.bottom(z=0.25), rate=0.5) + p1000.aspirate(LIGVol, LIG.bottom(z=0.1), rate=0.2) + p1000.default_speed = 5 + p1000.move_to(LIG.top(5)) + protocol.delay(seconds=0.2) + p1000.default_speed = 400 + p1000.dispense(LIGVol, sample_plate_1[X].bottom(z=0.1), rate=0.25) + p1000.move_to(sample_plate_1[X].bottom(z=0.25)) + p1000.mix(LIGMixRep, LIGMixVol, rate=0.25) + p1000.blow_out(sample_plate_1[X].top(z=-1)) + protocol.delay(seconds=0.2) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + if STEP_LIGDECK == 1: + protocol.comment("--> INCUBATING AT RT for 10 MIN") + protocol.delay(minutes=10) + + if STEP_POSTLIG == 1: + protocol.comment("==============================================") + protocol.comment("--> Cleanup 2") + protocol.comment("==============================================") + + # Setting Labware to Resume at Cleanup 2 + protocol.comment("--> ADDING AMPure (0.65x)") + AMPureVol = 40 + SampleVol = 140 + AMPureMixRPM = 1800 + AirMultiDispense = False + AMPureMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 # Seconds + AMPurePremix = 3 if DRYRUN == False else 1 + # ======== DISPENSE =========== + if AirMultiDispense == True: + tipcheck(200) + p1000.pick_up_tip() + p1000.mix(AMPurePremix, 40, AMPure.bottom(z=1)) + for loop, X in enumerate(column_2_list): + p1000.aspirate(AMPureVol, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(AMPureVol, sample_plate_1[X].top(z=1), rate=1) + protocol.delay(seconds=0.2) + p1000.blow_out(sample_plate_1[X].top(z=-1)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + else: + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(AMPureVol, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(AMPureVol, sample_plate_1[X].top(z=1), rate=1) + protocol.delay(seconds=0.2) + p1000.blow_out(sample_plate_1[X].top(z=-1)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + # ============================== + heatershaker.set_and_wait_for_shake_speed(rpm=AMPureMixRPM) + protocol.delay(AMPureMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATER SHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=MAG_PLATE_SLOT, use_gripper=True) + heatershaker.close_labware_latch() + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() # <---------------- Tip Pickup + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_1[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].top(z=2)) + p1000.default_speed = 200 + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM MAG PLATE to Heatershaker + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=heatershaker, use_gripper=True) + heatershaker.close_labware_latch() + # ============================================================================================ + # LFB wash 1 + for X in range(1): + protocol.comment("--> LFB Wash 1") + LFBVol = 61 + LFBMixRPM = 2000 + AirMultiDispense = True + LFBMixRep = 1 * 60 if DRYRUN == False else 0.1 * 60 + # ======== DISPENSE =========== + if AirMultiDispense == True: + + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(LFBVol, LFB.bottom(z=1)) + p1000.move_to(sample_plate_1.wells_by_name()[X].top(z=3)) + p1000.dispense(LFBVol, rate=2) + p1000.blow_out(sample_plate_1.wells_by_name()[X].top(z=3)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + else: + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() # <---------------- Tip Pickup + p1000.aspirate(LFBVol, LFB.bottom(z=1)) + p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) + p1000.dispense(LFBVol, rate=1) + p1000.blow_out(sample_plate_1.wells_by_name()[X].top()) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + # =============================== + heatershaker.set_and_wait_for_shake_speed(rpm=LFBMixRPM) + protocol.delay(LFBMixRep) + heatershaker.deactivate_shaker() + # ============================== + if DRYRUN == False: + protocol.delay(minutes=0.5) + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATER SHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=MAG_PLATE_SLOT, use_gripper=True) + heatershaker.close_labware_latch() + # ============================================================================================ + if DRYRUN == False: + protocol.delay(minutes=3) + # =================================================== + protocol.comment("--> Remove LFB Wash 1") + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_1[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].top(z=2)) + p1000.default_speed = 200 + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM MAG PLATE to Heatershaker + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=heatershaker, use_gripper=True) + heatershaker.close_labware_latch() + # ============================================================================================ + # LFB Wash 2 + for X in range(1): + protocol.comment("--> LFB wash 2") + LFBVol = 61 + LFBMixRPM = 2000 + AirMultiDispense = True + LFBMixRep = 1 * 60 if DRYRUN == False else 0.1 * 60 + # ======== DISPENSE =========== + if AirMultiDispense == True: + + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(LFBVol, LFB.bottom(z=1)) + p1000.move_to(sample_plate_1.wells_by_name()[X].top(z=3)) + p1000.dispense(LFBVol, rate=2) + p1000.blow_out(sample_plate_1.wells_by_name()[X].top(z=3)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + else: + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() # <---------------- Tip Pickup + p1000.aspirate(LFBVol, LFB.bottom(z=1)) + p1000.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) + p1000.dispense(LFBVol, rate=1) + p1000.blow_out(sample_plate_1.wells_by_name()[X].top()) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + # =============================== + heatershaker.set_and_wait_for_shake_speed(rpm=LFBMixRPM) + protocol.delay(LFBMixRep) + heatershaker.deactivate_shaker() + # ============================== + if DRYRUN == False: + protocol.delay(minutes=0.5) + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATER SHAKER TO MAG PLATE + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=MAG_PLATE_SLOT, use_gripper=True) + heatershaker.close_labware_latch() + # ============================================================================================ + if DRYRUN == False: + protocol.delay(minutes=3) + # =================================================== + protocol.comment("--> Remove LFB Wash 2") + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_1[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].top(z=2)) + p1000.default_speed = 200 + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + if DRYRUN == False: + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual LFB Wash") + for loop, X in enumerate(column_2_list): + tipcheck(50) + p50.pick_up_tip() # <---------------- Tip Pickup + p50.move_to(sample_plate_1[X].bottom(1)) + p50.aspirate(20, rate=0.25) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM MAG PLATE TO HEATER SHAKER + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=heatershaker, use_gripper=True) + heatershaker.close_labware_latch() + + # ============================================================================================ + protocol.comment("--> Adding Elution") + ElutVol = 18 + ElutMixRPM = 2000 + AirMultiDispense = True + ElutMixRep = 5 * 60 if DRYRUN == False else 0.1 * 60 + # ======== DISPENSE =========== + if AirMultiDispense == True: + tipcheck(50) + p50.pick_up_tip() + for loop, X in enumerate(column_2_list): + p50.aspirate(ElutVol, Elut.bottom(z=1)) + p50.move_to(sample_plate_1.wells_by_name()[X].top(z=0.5)) + p50.dispense(ElutVol, rate=1) + p50.blow_out(sample_plate_1.wells_by_name()[X].top(z=1)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + + else: + for loop, X in enumerate(column_2_list): + tipcheck(50) + p50.pick_up_tip() # <---------------- Tip Pickup + p50.aspirate(ElutVol, Elut.bottom(z=1)) + p50.move_to(sample_plate_1.wells_by_name()[X].bottom(z=1)) + p50.dispense(ElutVol, rate=1) + p50.blow_out(sample_plate_1.wells_by_name()[X].top()) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + + # ============================== + heatershaker.set_and_wait_for_shake_speed(rpm=ElutMixRPM) + protocol.delay(ElutMixRep) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM HEATER SHAKER TO Thermocycler + heatershaker.open_labware_latch() + protocol.move_labware(labware=sample_plate_1, new_location=thermocycler, use_gripper=True) + heatershaker.close_labware_latch() + # ============================================================================================ + if STEP_ELUTDECK == 1: + ############################################################################################################################################ + protocol.comment("Seal, Run ELUT (~10min)") + if DRYRUN == False: + thermocycler.set_lid_temperature(50) + thermocycler.close_lid() + profile_ERAT = [ + {"temperature": 37, "hold_time_minutes": 10}, + ] + thermocycler.execute_profile(steps=profile_ERAT, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(25) + ############################################################################################################################################ + thermocycler.open_lid() + protocol.pause("Remove Seal") + + # ============================================================================================ + # GRIPPER MOVE sample_plate_1 FROM Thermocycler TO MAG PLATE + protocol.move_labware(labware=sample_plate_1, new_location=MAG_PLATE_SLOT, use_gripper=True) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Transferring Supernatant") + TransferSup = 15 + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() # <---------------- Tip Pickup + p1000.move_to(sample_plate_1[X].bottom(z=0.5)) + p1000.aspirate(TransferSup, rate=0.25) + p1000.dispense(TransferSup, sample_plate_1[column_3_list[loop]].bottom(z=1)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + + if ABR_TEST == True: + protocol.comment("==============================================") + protocol.comment("--> Resetting Run") + protocol.comment("==============================================") + + heatershaker.open_labware_latch() + if DEACTIVATE_TEMP == True: + thermocycler.deactivate_block() + thermocycler.deactivate_lid() + temp_block.deactivate() + protocol.comment("Number of Resets: " + str(Resetcount)) + protocol.comment("Number of p200 Tips Used: " + str(p200_tips + (12 * p200_tipracks * p200_tipracks_count))) + protocol.comment("Number of p50 Tips Used: " + str(p50_tips + (12 * p50_tipracks * p50_tipracks_count))) diff --git a/app-testing/files/protocols/pl_NiNTA_Flex_96well_PlatePrep_final.py b/app-testing/files/protocols/pl_NiNTA_Flex_96well_PlatePrep_final.py new file mode 100644 index 00000000000..d777e32d478 --- /dev/null +++ b/app-testing/files/protocols/pl_NiNTA_Flex_96well_PlatePrep_final.py @@ -0,0 +1,214 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"NUM_COL":12,"ELUTION_TIMES":1,"BEADS_PRELOAD":1,"protocol_filename":"NiNTA_Flex_96well_PlatePrep_final"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point + +metadata = { + "protocolName": "Immobilized Metal Affinity Chromatography by Ni-NTA Magnetic Agarose Beads (plate preparation) - 96-well setting on Opentrons Flex", + "author": "Boren Lin, Opentrons", + "description": "This protocol prepares reagent plates for automated immobilized metal affinity chromatography (IMAC) using Ni-NTA magnetic agarose beads (up to 96 samples).", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.15", +} + +######################## + +NUM_COL = 12 +ELUTION_TIMES = 1 # 1 or 2 +BEADS_PRELOAD = 1 # NO: 0; YES: 1 + +ASP_HEIGHT = 0.2 +BEADS_VOL = 100 +EQUILIBRATION_VOL1 = 400 +EQUILIBRATION_VOL2 = 500 +SAMPLE_VOL = 500 +WASH_TIMES = 2 +WASH_VOL = 500 +ELUTION_VOL = 250 + +######################### + + +def run(ctx): + + global NUM_COL + global ELUTION_TIMES + global BEADS_PRELOAD + + try: + [NUM_COL, ELUTION_TIMES, BEADS_PRELOAD] = get_values("NUM_COL", "ELUTION_TIMES", "BEADS_PRELOAD") + except NameError: + # get_values is not defined + pass + + NUM_COL = int(NUM_COL) + ELUTION_TIMES = int(ELUTION_TIMES) + BEADS_PRELOAD = int(BEADS_PRELOAD) + + # load labware + + eql_res = ctx.load_labware("nest_96_wellplate_2ml_deep", "B2", "equilibration buffer") + wash_res = ctx.load_labware("nest_96_wellplate_2ml_deep", "C3", "wash buffer") + elution_res = ctx.load_labware("nest_96_wellplate_2ml_deep", "A2", "elution buffer") + + eql_stock = ctx.load_labware("nest_1_reservoir_290ml", "B1", "equilibration buffer") + wash_stock = ctx.load_labware("nest_1_reservoir_290ml", "B3", "wash buffer") + elution_stock = ctx.load_labware("nest_1_reservoir_290ml", "A1", "elution buffer") + + if BEADS_PRELOAD == 1: + beads_stock = ctx.load_labware("nest_12_reservoir_15ml", "D2", "beads") + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + working_plate = h_s_adapter.load_labware("nest_96_wellplate_2ml_deep", "working plate") + + temp = ctx.load_module("Temperature Module Gen2", "D3") + + tips = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "C2") + p1000 = ctx.load_instrument("flex_8channel_1000", "left", tip_racks=[tips]) + default_rate = 700 + p1000.flow_rate.aspirate = default_rate + p1000.flow_rate.dispense = default_rate + + # liquids + + eql_vol_source = (NUM_COL - 1) * (EQUILIBRATION_VOL1 + EQUILIBRATION_VOL2 + 50) * 8 + 24000 + eql_def = ctx.define_liquid(name="EQUILIBRATION", description="Equilibration Buffer", display_color="#9ACECB") ## Blue + eql_stock.wells()[0].load_liquid(liquid=eql_def, volume=eql_vol_source) + + wash_vol_source = (NUM_COL - 1) * (WASH_VOL * WASH_TIMES + 50) * 8 + 24000 + wash_def = ctx.define_liquid(name="WASH", description="Wash Buffer", display_color="#FF0000") ## Red + wash_stock.wells()[0].load_liquid(liquid=wash_def, volume=wash_vol_source) + + elu_vol_source = (NUM_COL - 1) * (ELUTION_VOL * ELUTION_TIMES + 50) * 8 + 24000 + elu_def = ctx.define_liquid(name="ELUTION", description="Elution Buffer", display_color="#00FFF2") ## Light Blue + elution_stock.wells()[0].load_liquid(liquid=elu_def, volume=elu_vol_source) + + eql = eql_res.rows()[0][:NUM_COL] + wash = wash_res.rows()[0][:NUM_COL] + elu = elution_res.rows()[0][:NUM_COL] # reagent 1 + + eql_source = eql_stock.wells()[0] + wash_source = wash_stock.wells()[0] + elu_source = elution_stock.wells()[0] + + if BEADS_PRELOAD == 1: + if NUM_COL <= 6: + beads_vol_stock = (NUM_COL - 1) * 8 * BEADS_VOL + 2000 + beads_def = ctx.define_liquid(name="BEADS", description="Ni-NTA Bead Slurry", display_color="#704848") ## Brown + beads_stock.wells()[0].load_liquid(liquid=beads_def, volume=beads_vol_stock) + else: + beads_vol_stock_1 = 6000 + beads_vol_stock_2 = (NUM_COL - 7) * 8 * BEADS_VOL + 2000 + beads_def_1 = ctx.define_liquid(name="BEADS", description="Ni-NTA Bead Slurry", display_color="#704848") ## Brown + beads_def_2 = ctx.define_liquid(name="BEADS", description="Ni-NTA Bead Slurry", display_color="#704848") ## Brown + beads_stock.wells()[0].load_liquid(liquid=beads_def_1, volume=beads_vol_stock_1) + beads_stock.wells()[1].load_liquid(liquid=beads_def_2, volume=beads_vol_stock_2) + + beads = beads_stock.wells()[0:2] + working_cols = working_plate.rows()[0][:NUM_COL] + + def transfer_beads(vol, end): + if NUM_COL <= 6: + start = beads[0] + p1000.pick_up_tip() + p1000.mix(5, vol * NUM_COL * 0.5, start.bottom(z=ASP_HEIGHT)) + p1000.mix(5, vol * NUM_COL * 0.5, start.bottom(z=ASP_HEIGHT * 2)) + p1000.aspirate(vol * NUM_COL, start.bottom(z=ASP_HEIGHT)) + p1000.air_gap(10) + end_loc = end[0] + p1000.flow_rate.dispense = 500 + p1000.dispense(10, end_loc.top(z=0)) + p1000.flow_rate.dispense = default_rate + for i in range(NUM_COL): + end_loc = end[i] + p1000.flow_rate.dispense = 500 + p1000.dispense(vol, end_loc.top(z=-5)) + p1000.flow_rate.dispense = default_rate + p1000.blow_out() + p1000.drop_tip() + else: + start1 = beads[0] + p1000.pick_up_tip() + p1000.mix(5, vol * 6 * 0.5, start1.bottom(z=ASP_HEIGHT)) + p1000.mix(5, vol * 6 * 0.5, start1.bottom(z=ASP_HEIGHT * 2)) + p1000.aspirate(vol * 6, start1.bottom(z=ASP_HEIGHT)) + p1000.air_gap(10) + end_loc = end[0] + p1000.flow_rate.dispense = 500 + p1000.dispense(10, end_loc.top(z=0)) + p1000.flow_rate.dispense = default_rate + for i in range(6): + end_loc = end[i] + p1000.flow_rate.dispense = 500 + p1000.dispense(vol, end_loc.top(z=-5)) + p1000.flow_rate.dispense = default_rate + p1000.blow_out() + + cols = NUM_COL - 6 + + start2 = beads[1] + p1000.mix(5, vol * cols * 0.5, start2.bottom(z=ASP_HEIGHT)) + p1000.mix(5, vol * cols * 0.5, start2.bottom(z=ASP_HEIGHT * 2)) + p1000.aspirate(vol * cols, start2.bottom(z=ASP_HEIGHT)) + p1000.air_gap(10) + end_loc = end[6] + p1000.flow_rate.dispense = 500 + p1000.dispense(10, end_loc.top(z=0)) + p1000.flow_rate.dispense = default_rate + for j in range(cols): + end_loc = end[j + 6] + p1000.flow_rate.dispense = 500 + p1000.dispense(vol, end_loc.top(z=-5)) + p1000.flow_rate.dispense = default_rate + p1000.blow_out() + p1000.drop_tip() + + def transfer_buffer(vol, start, end): + if vol > 800: + n = int(vol // 800) + if vol % 800 != 0: + n = n + 1 + p1000.pick_up_tip() + for _ in range(n): + for j in range(NUM_COL): + start_loc = start.bottom(z=ASP_HEIGHT).move(Point(x=j * 9 - 49.5)) + end_loc = end[j] + p1000.aspirate(vol / n, start_loc) + p1000.air_gap(10) + p1000.dispense(vol / n + 10, end_loc.top(z=-5)) + p1000.drop_tip() + else: + p1000.pick_up_tip() + for j in range(NUM_COL): + start_loc = start.bottom(z=ASP_HEIGHT).move(Point(x=j * 9 - 49.5)) + end_loc = end[j] + p1000.aspirate(vol, start_loc) + p1000.air_gap(10) + p1000.dispense(vol + 10, end_loc.top(z=-5)) + p1000.drop_tip() + + # protocol + + if BEADS_PRELOAD == 1: + ## working plate w/ beads + h_s.open_labware_latch() + ctx.pause("Move the Working Plate to the Shaker") + h_s.close_labware_latch() + transfer_beads(BEADS_VOL, working_cols) + h_s.open_labware_latch() + + ## wash buffer + transfer_buffer(WASH_VOL * WASH_TIMES + 50, wash_source, wash) + ## equilibration buffer + transfer_buffer(EQUILIBRATION_VOL1 + EQUILIBRATION_VOL2 + 50, eql_source, eql) + ## elution buffer + transfer_buffer(ELUTION_VOL * ELUTION_TIMES + 50, elu_source, elu) diff --git a/app-testing/files/protocols/pl_NiNTA_Flex_96well_final.py b/app-testing/files/protocols/pl_NiNTA_Flex_96well_final.py new file mode 100644 index 00000000000..0267fd4b9dd --- /dev/null +++ b/app-testing/files/protocols/pl_NiNTA_Flex_96well_final.py @@ -0,0 +1,306 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"NUM_COL":12,"INCUBATION_ON_DECK":1,"INCUBATION_MIN":60,"ELUTION_TIMES":1,"BEADS_PRELOAD":1,"protocol_filename":"NiNTA_Flex_96well_final"}""" + ) + return [_all_values[n] for n in names] + + +metadata = { + "protocolName": "Immobilized Metal Affinity Chromatography by Ni-NTA Magnetic Agarose Beads - 96-well setting on Opentrons Flex", + "author": "Boren Lin, Opentrons", + "description": "The protocol automates immobilized metal affinity chromatography (IMAC) using Ni-NTA magnetic agarose beads to isolate a protein of interest from liquid samples (up to 96 samples).", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.15", +} + +######################## + +NUM_COL = 12 +INCUBATION_ON_DECK = 1 # Yes:1; No:0 +INCUBATION_MIN = 60 +ELUTION_TIMES = 1 # 1 or 2 +BEADS_PRELOAD = 0 # Yes: 1; No: 0 + +# if on deck: +INCUBATION_SPEEND = 1000 + +ASP_HEIGHT = 0.2 +MIX_SPEEND = 2000 +MIX_SEC = 10 +ELUTION_SPEEND = 1000 +ELUTION_MIN = 10 + +MAG_DELAY_MIN = 2 + +BEADS_VOL = 100 +EQUILIBRATION_VOL1 = 400 +EQUILIBRATION_VOL2 = 500 +SAMPLE_VOL = 500 +WASH_TIMES = 2 +WASH_VOL = 500 +ELUTION_VOL = 250 + +WASTE_VOL_MAX = 275000 + +COOLING_DELAY_MIN = 3 + +USE_GRIPPER = True + +waste_vol_chk = 0 + +######################### + + +def run(ctx): + + global NUM_COL + global INCUBATION_ON_DECK + global INCUBATION_MIN + global ELUTION_TIMES + global BEADS_PRELOAD + + try: + [NUM_COL, INCUBATION_ON_DECK, INCUBATION_MIN, ELUTION_TIMES, BEADS_PRELOAD] = get_values( + "NUM_COL", "INCUBATION_ON_DECK", "INCUBATION_MIN", "ELUTION_TIMES", "BEADS_PRELOAD" + ) + except NameError: + # get_values is not defined + pass + + NUM_COL = int(NUM_COL) + INCUBATION_ON_DECK = int(INCUBATION_ON_DECK) + INCUBATION_MIN = int(INCUBATION_MIN) + ELUTION_TIMES = int(ELUTION_TIMES) + BEADS_PRELOAD = int(BEADS_PRELOAD) + + # load labware + sample_plate = ctx.load_labware("nest_96_wellplate_2ml_deep", "B3", "samples") + if BEADS_PRELOAD == 0: + beads_res = ctx.load_labware("nest_96_wellplate_2ml_deep", "B1", "beads") + eql_res = ctx.load_labware("nest_96_wellplate_2ml_deep", "B2", "equilibration buffer") + wash_res = ctx.load_labware("nest_96_wellplate_2ml_deep", "C3", "wash buffer") + elution_res = ctx.load_labware("nest_96_wellplate_2ml_deep", "A2", "elution buffer") + waste_res = ctx.load_labware("nest_1_reservoir_290ml", "D2", "waste") + + tips_reused = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "C2") + tips_reused_loc = tips_reused.wells()[:96] + tips_elu = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "elution tips") + tips_elu_loc = tips_elu.wells()[:96] + p1000 = ctx.load_instrument("flex_8channel_1000", "left") + default_rate = 700 + p1000.flow_rate.aspirate = default_rate + p1000.flow_rate.dispense = default_rate + + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + working_plate = h_s_adapter.load_labware("nest_96_wellplate_2ml_deep", "working plate") + + temp = ctx.load_module("Temperature Module Gen2", "D3") + final_plate = temp.load_labware("opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep", "final plate") + + mag = ctx.load_module("magneticBlockV1", "C1") + + # liquids + sample_vol_stock = SAMPLE_VOL + 50 + sample_def = ctx.define_liquid(name="SAMPLES", description="Sample per well", display_color="#52AAFF") ## Blue + for well_count in range(NUM_COL * 8): + sample_plate.wells()[well_count].load_liquid(liquid=sample_def, volume=sample_vol_stock / (NUM_COL * 8)) + + eql_vol_res = EQUILIBRATION_VOL1 + EQUILIBRATION_VOL2 + 50 + eql_def = ctx.define_liquid(name="EQUILIBRATION", description="Equilibration Buffer per Well", display_color="#9ACECB") ## Blue + for well_count in range(NUM_COL * 8): + eql_res.wells()[well_count].load_liquid(liquid=eql_def, volume=eql_vol_res / (NUM_COL * 8)) + + wash_vol_res = WASH_VOL * WASH_TIMES + 50 + wash_def = ctx.define_liquid(name="WASH", description="Wash Buffer per Well", display_color="#FF0000") ## Red + for well_count in range(NUM_COL * 8): + wash_res.wells()[well_count].load_liquid(liquid=wash_def, volume=wash_vol_res / (NUM_COL * 8)) + + elu_vol_res = ELUTION_VOL * ELUTION_TIMES + 50 + elu_def = ctx.define_liquid(name="ELUTION", description="Elution Buffer per Well", display_color="#00FFF2") ## Light Blue + for well_count in range(NUM_COL * 8): + elution_res.wells()[well_count].load_liquid(liquid=elu_def, volume=elu_vol_res / (NUM_COL * 8)) + + beads_vol_res = BEADS_VOL + 50 + if BEADS_PRELOAD == 0: + beads_def = ctx.define_liquid(name="BEADS", description="Ni-NTA Bead Slurry", display_color="#704848") ## Brown + for well_count in range(NUM_COL * 8): + beads_res.wells()[well_count].load_liquid(liquid=beads_def, volume=beads_vol_res / (NUM_COL * 8)) + else: + beads_def = ctx.define_liquid(name="BEADS", description="Ni-NTA Bead Slurry in Working Plate", display_color="#704848") ## Brown + for well_count in range(NUM_COL * 8): + working_plate.wells()[well_count].load_liquid(liquid=beads_def, volume=beads_vol_res / (NUM_COL * 8)) + + samples = sample_plate.rows()[0][:NUM_COL] + if BEADS_PRELOAD == 0: + beads = beads_res.rows()[0][:NUM_COL] # reagent 2 + eql = eql_res.rows()[0][:NUM_COL] + wash = wash_res.rows()[0][:NUM_COL] + elu = elution_res.rows()[0][:NUM_COL] # reagent 1 + waste = waste_res.wells()[0] + + working_cols = working_plate.rows()[0][:NUM_COL] # reagent 3 + final_cols = final_plate.rows()[0][:NUM_COL] + + def transfer(vol1, start, end, reagent=0): + for i in range(NUM_COL): + if reagent == 1 or reagent == 3: + p1000.pick_up_tip(tips_elu_loc[i * 8]) + else: + p1000.pick_up_tip(tips_reused_loc[i * 8]) + start_loc = start[i] + end_loc = end[i] + if reagent == 2: + p1000.mix(5, vol1 * 0.75, start_loc.bottom(z=ASP_HEIGHT * 2)) + if reagent == 3: + p1000.flow_rate.aspirate = 100 + p1000.aspirate(vol1, start_loc.bottom(z=ASP_HEIGHT)) + p1000.flow_rate.aspirate = default_rate + else: + p1000.aspirate(vol1, start_loc.bottom(z=ASP_HEIGHT)) + p1000.air_gap(10) + p1000.dispense(vol1 + 10, end_loc.top(z=-5)) + p1000.touch_tip() + p1000.blow_out() + p1000.return_tip() + + def mix(speend, time): + ctx.comment("\n\n\n~~~~~~~~Shake to mix~~~~~~~~\n") + h_s.set_and_wait_for_shake_speed(rpm=speend) + ctx.delay(seconds=time) + h_s.deactivate_shaker() + + def discard(vol2, start): + global waste_vol + global waste_vol_chk + if waste_vol_chk >= WASTE_VOL_MAX: + ctx.pause("Empty Liquid Waste") + waste_vol_chk = 0 + waste_vol = 0 + for j in range(NUM_COL): + p1000.pick_up_tip(tips_reused_loc[j * 8]) + start_loc = start[j] + end_loc = waste + p1000.flow_rate.aspirate = 100 + p1000.aspirate(vol2, start_loc.bottom(z=ASP_HEIGHT)) + p1000.flow_rate.aspirate = default_rate + p1000.air_gap(10) + p1000.dispense(vol2 + 10, end_loc.top(z=2)) + p1000.blow_out() + p1000.return_tip() + waste_vol = vol2 * NUM_COL * 8 + waste_vol_chk = waste_vol_chk + waste_vol + + # protocol + + ## Equilibration + h_s.open_labware_latch() + ctx.pause("Move the Working Plate to the Shaker") + h_s.close_labware_latch() + + if BEADS_PRELOAD == 0: + transfer(BEADS_VOL, beads, working_cols, 2) + transfer(EQUILIBRATION_VOL1, eql, working_cols) + mix(MIX_SPEEND, MIX_SEC) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + discard(EQUILIBRATION_VOL1 * 1.1 + BEADS_VOL, working_cols) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer(EQUILIBRATION_VOL2, eql, working_cols) + mix(MIX_SPEEND, MIX_SEC) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + discard(EQUILIBRATION_VOL2 * 1.1, working_cols) + + ## Protein Capture + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer(SAMPLE_VOL, samples, working_cols) + h_s.set_and_wait_for_shake_speed(rpm=MIX_SPEEND) + ctx.delay(seconds=MIX_SEC) + + if INCUBATION_ON_DECK == 1: + mix(INCUBATION_SPEEND, INCUBATION_MIN * 60) + h_s.open_labware_latch() + + else: + # incubation off deck + h_s.deactivate_shaker() + h_s.open_labware_latch() + ctx.pause("Seal the Plate") + ctx.pause("Remove the Seal, Move the Plate back to Shaker") + + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + ctx.delay(minutes=MAG_DELAY_MIN) + discard(SAMPLE_VOL * 1.1, working_cols) + + ## Wash + for _ in range(WASH_TIMES): + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer(WASH_VOL, wash, working_cols) + mix(MIX_SPEEND, MIX_SEC) + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + discard(WASH_VOL * 1.1, working_cols) + + ## Elution + for j in range(ELUTION_TIMES): + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Shaker') + ctx.move_labware(labware=working_plate, new_location=h_s_adapter, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + + transfer(ELUTION_VOL, elu, working_cols, 1) + h_s.set_and_wait_for_shake_speed(rpm=MIX_SPEEND) + ctx.delay(seconds=MIX_SEC) + h_s.set_and_wait_for_shake_speed(rpm=ELUTION_SPEEND) + if j == 0: + temp.set_temperature(4) + delay = ELUTION_MIN - COOLING_DELAY_MIN + ctx.delay(seconds=delay * 60) + else: + delay = ELUTION_MIN + ctx.delay(seconds=delay * 60) + h_s.deactivate_shaker() + + h_s.open_labware_latch() + # ctx.pause('Move the Working Plate to the Magnet') + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + h_s.close_labware_latch() + ctx.delay(minutes=MAG_DELAY_MIN) + transfer(ELUTION_VOL * 1.1, working_cols, final_cols, 3) + + ctx.pause("Protocol Complete") + temp.deactivate() diff --git a/app-testing/files/protocols/pl_Normalization_with_PCR.py b/app-testing/files/protocols/pl_Normalization_with_PCR.py new file mode 100644 index 00000000000..88254fbc17a --- /dev/null +++ b/app-testing/files/protocols/pl_Normalization_with_PCR.py @@ -0,0 +1,277 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"csv_samp":"Destination Well,Water Transfer Volume (ul),DNA Transfer Volume (ul),Mastermix Volume (ul),Mastermix Source Tube\\nA1,3,7,40,A1\\nB1,3,7,40,A1\\nC1,3,7,40,A1\\n","temp_mod_on":true,"rxn_vol":50,"p50_mount":"right","protocol_filename":"Normalization_with_PCR"}""" + ) + return [_all_values[n] for n in names] + + +# flake8: noqa + +from opentrons import protocol_api +from opentrons import types +import random +import math + +metadata = { + "ctx.Name": "PCR Protocol", + "author": "Rami Farawi "} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + trash_chute = False # If false, waste bin loaded in D3, if True, trash chute loaded there + USE_GRIPPER = True + dry_run = False + temp_mod = True + heater_shaker = True + tip_mixing = False + + wash_vol = 600 + AL_vol = 230 + TL_vol = 250 + bind_vol = 320 + sample_vol = 200 # amount transferred from TL plate to sample plate + elution_vol = 100 + + try: + [ + trash_chute, + USE_GRIPPER, + dry_run, + temp_mod, + heater_shaker, + tip_mixing, + wash_vol, + sample_vol, + bind_vol, + TL_vol, + AL_vol, + elution_vol, + ] = get_values( # noqa: F821 + "trash_chute", + "USE_GRIPPER", + "dry_run", + "temp_mod", + "heater_shaker", + "tip_mixing", + "wash_vol", + "sample_vol", + "bind_vol", + "TL_vol", + "AL_vol", + "elution_vol", + ) + + except NameError: + pass + + # Just to be safe + if heater_shaker: + tip_mixing = False + + # Same for all HDQ Extractions + deepwell_type = "nest_96_wellplate_2ml_deep" + if not dry_run: + settling_time = 2 + num_washes = 3 + if dry_run: + settling_time = 0.5 + num_washes = 1 + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + PK_vol = bead_vol = 20 + inc_temp = 55 + starting_vol = AL_vol + sample_vol + TL_total_vol = TL_vol + PK_vol + binding_buffer_vol = bind_vol + bead_vol + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + TL_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate 1") + else: + TL_plate = ctx.load_labware(deepwell_type, "D1", "Sample Plate 1") + + TL_samples = TL_plate.wells()[0] + + sample_plate = ctx.load_labware(deepwell_type, "C3", "Sample Plate 2") + samples_m = sample_plate.wells()[0] + + samples = ctx.define_liquid(name="Sample", description="Sample Resuspended in PBS", display_color="#FF0000") + for well in sample_plate.wells(): + well.load_liquid(liquid=samples, volume=sample_vol) + + if temp_mod: + temp = ctx.load_module("temperature module gen2", "A3") + temp_block = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = temp_block.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate/ Reservoir") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate/ Reservoir") + magblock = ctx.load_module("magneticBlockV1", "C1") + # elution_two = ctx.load_labware(deepwell_type, '12').wells()[0] + + #'#008000','#A52A2A','#00FFFF','#0000FF','#800080','#ADD8E6','#FF0000','#FFFF00','#FF00FF','#00008B','#7FFFD4','#FFC0CB','#FFA500','#00FF00','#C0C0C0' + + TL_reservoir = ctx.load_labware(deepwell_type, "D2", "TL reservoir") + TL_res = TL_reservoir.wells()[0] + TL_buffer = ctx.define_liquid(name="TL Buffer", description="TL Buffer", display_color="#008000") + for well in TL_reservoir.wells(): + well.load_liquid(liquid=TL_buffer, volume=TL_vol + 94.5) + + PK_buffer = ctx.define_liquid(name="PK Buffer", description="PK Buffer", display_color="#008000") + for well in TL_reservoir.wells(): + well.load_liquid(liquid=PK_buffer, volume=PK_vol + 7.5) + + AL_reservoir = ctx.load_labware(deepwell_type, "C2", "AL reservoir") + AL_res = AL_reservoir.wells()[0] + AL_buffer = ctx.define_liquid(name="AL Buffer", description="AL Buffer", display_color="#A52A2A") + for well in AL_reservoir.wells(): + well.load_liquid(liquid=AL_buffer, volume=AL_vol + 100) + + wash1_reservoir = ctx.load_labware(deepwell_type, "B1", "Wash 1 (VHB) reservoir") + wash1_res = wash1_reservoir.wells()[0] + wash1_buffer = ctx.define_liquid(name="VHB Buffer", description="Wash 1 Buffer", display_color="#00FFFF") + for well in wash1_reservoir.wells(): + well.load_liquid(liquid=wash1_buffer, volume=(2 * wash_vol) + 100) + + wash2_reservoir = ctx.load_labware(deepwell_type, "B2", "Wash 2 (SPM) reservoir") + wash2_res = wash2_reservoir.wells()[0] + wash2_buffer = ctx.define_liquid(name="SPM Buffer", description="Wash 2 Buffer", display_color="#0000FF") + for well in wash2_reservoir.wells(): + well.load_liquid(liquid=wash2_buffer, volume=wash_vol + 100) + + bind_reservoir = ctx.load_labware(deepwell_type, "B3", "Beads and Binding reservoir") + bind_res = bind_reservoir.wells()[0] + bind_buffer = ctx.define_liquid(name="Bind Buffer", description="Bind Buffer", display_color="#800080") + for well in bind_reservoir.wells(): + well.load_liquid(liquid=bind_buffer, volume=bind_vol + 100) + + elution_res = elutionplate.wells()[0] + elution_buffer = ctx.define_liquid(name="Elution Buffer", description="Elution Buffer", display_color="#ADD8E6") + for well in elutionplate.wells(): + well.load_liquid(liquid=elution_buffer, volume=elution_vol + 5) + + # Load tips + tipsbox = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", adapter="opentrons_flex_96_tiprack_adapter") + tips = tipsbox.wells()[0] + tips1box = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", adapter="opentrons_flex_96_tiprack_adapter") + tips1 = tips1box.wells()[0] + + # load 96 channel pipette + pip = ctx.load_instrument("flex_96channel_1000", mount="left") + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + pip.flow_rate.blow_out = 300 + + def resuspend_pellet(vol, plate, reps=3): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=1, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0.75, y=0.75, z=1)) + loc3 = plate.bottom().move(types.Point(x=0, y=1, z=1)) + loc4 = plate.bottom().move(types.Point(x=-0.75, y=0.75, z=1)) + loc5 = plate.bottom().move(types.Point(x=-1, y=0, z=1)) + loc6 = plate.bottom().move(types.Point(x=-0.75, y=0 - 0.75, z=1)) + loc7 = plate.bottom().move(types.Point(x=0, y=-1, z=1)) + loc8 = plate.bottom().move(types.Point(x=0.75, y=-0.75, z=1)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc2) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc3) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc4) + pip.dispense(mixvol, loc4) + pip.aspirate(mixvol, loc5) + pip.dispense(mixvol, loc5) + pip.aspirate(mixvol, loc6) + pip.dispense(mixvol, loc6) + pip.aspirate(mixvol, loc7) + pip.dispense(mixvol, loc7) + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + def bead_mix(vol, plate, reps=5): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=0, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0, y=0, z=8)) + loc3 = plate.bottom().move(types.Point(x=0, y=0, z=16)) + loc4 = plate.bottom().move(types.Point(x=0, y=0, z=24)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc4) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + # Just in case + if heater_shaker: + h_s.close_labware_latch() + + # Start Protocol + pip.pick_up_tip(tips) + # Mix PK and TL buffers + ctx.comment("----- Mixing TL buffer and PK -----") + for m in range(3 if not dry_run else 1): + pip.aspirate(TL_total_vol, TL_res) + pip.dispense(TL_total_vol, TL_res.bottom(30)) + # Transfer TL to plate + ctx.comment("----- Transferring TL and PK to samples -----") + pip.aspirate(TL_total_vol, TL_res) + pip.air_gap(10) + pip.dispense(pip.current_volume, TL_samples) + if heater_shaker and not dry_run: + h_s.set_target_temperature(55) + ctx.comment("----- Mixing TL buffer with samples -----") + resuspend_pellet(TL_total_vol, TL_samples, reps=9 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + ctx.comment("----- Mixing and incubating for 30 minutes on Heater-Shaker -----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=30 if not dry_run else 0.25, msg="Shake at 2000 rpm for 30 minutes to allow lysis.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + for x in range(4): + bead_mix(TL_total_vol, samples_m, reps=8) + ctx.delay(minutes=2) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 30 minutes at 2000 rpm.") + + # Transfer 200ul of sample + TL buffer to sample plate + ctx.comment("----- Mixing, the transferring 200 ul of sample to new deep well plate -----") + pip.pick_up_tip(tips) + pip.aspirate(sample_vol, TL_samples) + pip.air_gap(20) + pip.dispense(pip.current_volume, samples_m) + pip.blow_out() + pip.return_tip() + + # Move TL samples off H-S into deck slot and sample plate onto H-S + ctx.comment("------- Transferring TL and Sample plates -------") + # Transfer TL samples from H-S to Magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(TL_plate, magblock, use_gripper=USE_GRIPPER) + # Move sample plate onto H-S from deck + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + # Move plate off magplate onto the deck + ctx.move_labware(TL_plate, 6, use_gripper=USE_GRIPPER) + + # Transfer and mix AL_lysis + ctx.comment("------- Starting AL Lysis Steps -------") + pip.pick_up_tip(tips) + pip.aspirate(AL_vol, AL_res) + pip.air_gap(10) + pip.dispense(pip.current_volume, samples_m) + resuspend_pellet(starting_vol, samples_m, reps=9 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + # Mix, then heat + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=4 if not dry_run else 0.25, msg="Please wait 4 minutes to allow for proper lysis mixing.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(AL_vol, samples_m, reps=9) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 4 minutes at 2000 rpm.") + + # Transfer and mix bind&beads + ctx.comment("------- Mixing and Transferring Beads and Binding -------") + pip.pick_up_tip(tips) + bead_mix(binding_buffer_vol, bind_res, reps=3 if not dry_run else 1) + pip.aspirate(binding_buffer_vol, bind_res) + pip.dispense(binding_buffer_vol, samples_m) + bead_mix(binding_buffer_vol + starting_vol, samples_m, reps=3 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + pip.home() + + # Shake for binding incubation + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please allow 10 minutes for the beads to bind the DNA.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + for x in range(2): + bead_mix(binding_buffer_vol, samples_m, reps=10) + ctx.delay(minutes=2) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 10 minutes at 1800 rpm.") + + if heater_shaker: + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + # Remove Supernatant and move off magnet + ctx.comment("------- Removing Supernatant -------") + pip.pick_up_tip(tips) + pip.aspirate(1000, samples_m.bottom(0.5)) + pip.dispense(1000, bind_res) + if starting_vol + binding_buffer_vol > 1000: + pip.aspirate(1000, samples_m.bottom(0.5)) + pip.dispense(1000, bind_res) + pip.return_tip() + + # Transfer plate from magnet to H/S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # Washes + for i in range(num_washes): + if i == 0 or 1: + wash_res = wash1_res + waste_res = TL_res + if i == 2: + wash_res = wash2_res + waste_res = bind_res + ctx.comment("------- Starting Wash #" + str(i + 1) + " -------") + pip.pick_up_tip(tips) + pip.aspirate(wash_vol, wash_res) + pip.dispense(wash_vol, samples_m) + # resuspend_pellet(wash_vol,samples_m,reps=1 if not dry_run else 1) + pip.blow_out() + pip.air_gap(10) + if not tip_mixing: + pip.return_tip() + pip.home() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=5 if not dry_run else 0.25) + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(wash_vol, samples_m, reps=12) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 1800 rpm.") + + # Transfer plate to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + # Remove Supernatant and move off magnet + ctx.comment("------- Removing Supernatant -------") + pip.pick_up_tip(tips) + pip.aspirate(1000, samples_m.bottom(0.5)) + pip.dispense(1000, waste_res.top()) + if wash_vol > 1000: + pip.aspirate(1000, samples_m.bottom(0.5)) + pip.dispense(1000, waste_res.top()) + pip.return_tip() + + # Transfer plate from magnet to H/S after first two washes + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + if not dry_run: + dry_beads = 10 + else: + dry_beads = 0.5 + + for beaddry in np.arange(dry_beads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + + # Elution + ctx.comment("------- Beginning Elution Steps -------") + + pip.pick_up_tip(tips1) + pip.aspirate(elution_vol, elution_res) + pip.dispense(elution_vol, samples_m) + resuspend_pellet(elution_vol, samples_m, reps=3 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + pip.home() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please wait 5 minutes to allow dna to elute from beads.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(elution_vol, samples_m, reps=12) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 2000 rpm.") + + # Transfer plate to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + pip.pick_up_tip(tips1) + pip.aspirate(elution_vol, samples_m) + pip.dispense(elution_vol, elution_res) + pip.return_tip() + + pip.home() diff --git a/app-testing/files/protocols/pl_Omega_HDQ_DNA_Bacteria_Flex_multi.py b/app-testing/files/protocols/pl_Omega_HDQ_DNA_Bacteria_Flex_multi.py new file mode 100644 index 00000000000..32c6be93a84 --- /dev/null +++ b/app-testing/files/protocols/pl_Omega_HDQ_DNA_Bacteria_Flex_multi.py @@ -0,0 +1,679 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"dry_run":false,"mount":"left","temp_mod":true,"heater_shaker":true,"res_type":"nest_12_reservoir_15ml","num_samples":8,"wash1_vol":600,"wash2_vol":600,"wash3_vol":600,"TL_vol":250,"AL_vol":230,"sample_vol":200,"bind_vol":320,"e_one_vol":350,"elution_vol":100,"protocol_filename":"Omega_HDQ_DNA_Bacteria-Flex_multi"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +import json +import math +from opentrons import types +import numpy as np + +metadata = {"author": "Zach Galluzzo "} + +requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +""" +Here is where you can modify the magnetic module engage height: +""" +whichwash = 1 +tip1k = 0 +drop_count = 0 +sample_max = 48 +waste_vol = 0 + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + dry_run = True + trash_chute = False # If this is true, trash chute is loaded in D3, otherwise trash bin is loaded there + USE_GRIPPER = True + mount = "left" + res_type = "nest_12_reservoir_15ml" + temp_mod = True # True or false if you have a temp mod loaded on deck with the elution plate + heater_shaker = True + + num_samples = 48 + wash1_vol = 600 + wash2_vol = 600 + wash3_vol = 600 + AL_vol = 230 + TL_vol = 250 + sample_vol = 200 + bind_vol = 320 + e_one_vol = 350 + elution_vol = 100 + + try: + [ + res_type, + temp_mod, + trash_chute, + USE_GRIPPER, + dry_run, + mount, + num_samples, + heater_shaker, + wash1_vol, + wash2_vol, + wash3_vol, + TL_vol, + AL_vol, + sample_vol, + bind_vol, + e_one_vol, + elution_vol, + ] = get_values( # noqa: F821 + "res_type", + "temp_mod", + "trash_chute", + "USE_GRIPPER", + "dry_run", + "mount", + "num_samples", + "heater_shaker", + "wash1_vol", + "wash2_vol", + "wash3_vol", + "TL_vol", + "AL_vol", + "sample_vol", + "bind_vol", + "e_one_vol", + "elution_vol", + ) + + except NameError: + pass + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + + if not dry_run: + settling_time = 2 + else: + settling_time = 0.25 + bead_vol = PK_vol = 20 + TL_total_vol = TL_vol + PK_vol + starting_vol = AL_vol + sample_vol + binding_buffer_vol = bind_vol + bead_vol + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") + h_s.close_labware_latch() + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Sample Plate") + + if temp_mod: + temp = ctx.load_module("temperature module gen2", "A3") + tempblock = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = tempblock.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate") + + magblock = ctx.load_module("magneticBlockV1", "C1") + waste = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste").wells()[0].top() + res1 = ctx.load_labware(res_type, "D2", "reagent reservoir 1") + res2 = ctx.load_labware(res_type, "C2", "reagent reservoir 2") + num_cols = math.ceil(num_samples / 8) + + # Load tips and combine all similar boxes + tips1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "Tips 1") + tips1001 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "Tips 2") + tips1002 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B1", "Tips 3") + tips1003 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B2", "Tips 4") + tips = [*tips1000.wells()[8 * (num_cols) : 96], *tips1001.wells(), *tips1002.wells(), *tips1003.wells()] + tips_sn = tips1000.wells()[: 8 * (num_cols)] + + # load instruments + m1000 = ctx.load_instrument("flex_8channel_1000", mount) + + """ + Here is where you can define the locations of your reagents. + """ + binding_buffer = res1.wells()[:2] + elution_one = res1.wells()[10:] + wash1 = res2.wells()[:3] + wash2 = res2.wells()[3:6] + wash3 = res2.wells()[6:9] + AL = res1.wells()[2] + TL = res1.wells()[3:5] + + samples_m = sample_plate.rows()[0][:num_cols] + TL_samples_m = sample_plate.rows()[0][num_cols : 2 * num_cols] + elution_samples_m = elutionplate.rows()[0][:num_cols] + + colors = [ + "#008000", + "#A52A2A", + "#A52A2A", + "#00FFFF", + "#00FFFF", + "#0000FF", + "#800080", + "#ADD8E6", + "#00008B", + "#FF0000", + "#FFFF00", + "#FF00FF", + "#7FFFD4", + "#FFC0CB", + "#FFA500", + "#00FF00", + "#C0C0C0", + ] + + # Begin with assigning plate wells before reservoir wells + samps = ctx.define_liquid(name="Samples", description="Samples", display_color="#FFA500") + elution_samps = ctx.define_liquid(name="Elution buffer", description="Elution buffer", display_color="#00FF00") + + for well in sample_plate.wells()[:num_samples]: + well.load_liquid(liquid=samps, volume=sample_vol) + + for well in elutionplate.wells()[:num_samples]: + well.load_liquid(liquid=elution_samps, volume=elution_vol) + + # Start defining reservoir wells + locations = [AL, TL, TL, binding_buffer, binding_buffer, wash1, wash2, wash3, elution_one] + vols = [AL_vol, PK_vol, TL_vol, bead_vol, bind_vol, wash1_vol, wash2_vol, wash3_vol, e_one_vol] + liquids = ["AL Lysis", "PK", "TL Lysis", "Beads", "Binding", "Wash 1", "Wash 2", "Wash 3", "Elution One"] + + delete = len(colors) - len(liquids) + + if delete >= 1: + for i in range(delete): + colors.pop(-1) + + def liquids_(liq, location, color, vol): + sampnum = 8 * (math.ceil(num_samples / 8)) + """ + Takes an individual liquid at a time and adds the color to the well + in the description. + """ + # Volume Calculation + if liq == "PK": + extra_samples = math.ceil(1500 / TL_vol) + + elif liq == "Beads": + extra_samples = math.ceil(1500 / bind_vol) + + else: + extra_samples = math.ceil(1500 / vol) + + # Defining and assigning liquids to wells + if isinstance(location, list): + limit = sample_max / len(location) # Calculates samples/ res well + iterations = math.ceil(sampnum / limit) + left = sampnum - limit + while left > limit: + left = left - limit + if left > 0: + last_iteration_samp_num = left + elif left < 0: + last_iteration_samp_num = sampnum + else: + last_iteration_samp_num = limit + + samples_per_well = [] + + for i in range(iterations): + # append the left over on the last iteration + if i == (iterations - 1): + samples_per_well.append(last_iteration_samp_num) + else: + samples_per_well.append(limit) + + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + for sample, well in zip(samples_per_well, location[: len(samples_per_well)]): + v = vol * (sample + extra_samples) + well.load_liquid(liquid=liq, volume=v) + else: + v = vol * (sampnum + extra_samples) + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + location.load_liquid(liquid=liq, volume=v) + + for x, (ll, l, c, v) in enumerate(zip(liquids, locations, colors, vols)): + liquids_(ll, l, c, v) + + m1000.flow_rate.aspirate = 300 + m1000.flow_rate.dispense = 300 + m1000.flow_rate.blow_out = 300 + + def blink(): + for i in range(3): + ctx.set_rail_lights(True) + ctx.delay(minutes=0.01666667) + ctx.set_rail_lights(False) + ctx.delay(minutes=0.01666667) + + def tiptrack(pip, tipbox): + global tip1k + global tip200 + global drop_count + if tipbox == tips: + m1000.pick_up_tip(tipbox[int(tip1k)]) + tip1k = tip1k + 8 + drop_count = drop_count + 8 + if drop_count >= 250: + drop_count = 0 + ctx.pause("Please empty the waste bin of all the tips before continuing.") + + def remove_supernatant(vol): + ctx.comment("-----Removing Supernatant-----") + m1000.flow_rate.aspirate = 150 + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + + def _waste_track(vol): + global waste_vol + waste_vol = waste_vol + (vol * 8) + if waste_vol >= 185000: + m1000.home() + blink() + ctx.pause("Please empty liquid waste before resuming.") + waste_vol = 0 + + for i, m in enumerate(samples_m): + m1000.pick_up_tip(tips_sn[8 * i]) + loc = m.bottom(0.5) + for _ in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, m.top()) + m1000.move_to(m.center()) + m1000.transfer(vol_per_trans, loc, waste, new_tip="never", air_gap=20) + _waste_track(vol_per_trans) + m1000.blow_out(waste) + m1000.air_gap(20) + m1000.drop_tip(tips_sn[8 * i]) + m1000.flow_rate.aspirate = 300 + + def bead_mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top().move(types.Point(x=0, y=0, z=5)) + aspbot = well.bottom().move(types.Point(x=0, y=3, z=1)) + asptop = well.bottom().move(types.Point(x=0, y=-3, z=5)) + disbot = well.bottom().move(types.Point(x=0, y=2, z=3)) + distop = well.top().move(types.Point(x=0, y=1, z=0)) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, aspbot) + pip.dispense(vol, distop) + pip.aspirate(vol, asptop) + pip.dispense(vol, disbot) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, aspbot) + pip.dispense(vol, aspbot) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(1) + disp = well.top(-8) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def TL_lysis(vol, source): + ctx.comment("-----Treansferring TL Buffer-----") + num_transfers = math.ceil(vol / 980) + tiptrack(m1000, tips) + for i in range(num_cols): + src = source[i // 3] # spread across 2 wells of reservoir + tvol = vol / num_transfers + if i == 0: + for x in range(3): + m1000.aspirate(tvol, src.bottom(1)) + m1000.dispense(tvol, src.bottom(5)) + for t in range(num_transfers): + m1000.aspirate(tvol, src.bottom(1)) + m1000.air_gap(20) + m1000.dispense(m1000.current_volume, TL_samples_m[i].top()) + + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + mixing(TL_samples_m[i], m1000, tvol - 50, reps=10 if not dry_run else 1) + m1000.drop_tip() if not dry_run else m1000.return_tip() + + ctx.comment("-----Mixing Buffer and Sample-----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1800) + h_s.set_and_wait_for_temperature(55) + ctx.delay(minutes=40 if not dry_run else 0.25, msg="Shake at 1800 rpm for 40 minutes.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 55C and 1800 rpm for 40 minutes.") + + # Transfer 200ul of sample+TL to sample columns + ctx.comment("-----Transferring sample and TL buffer to new well-----") + for t in range(num_cols): + tiptrack(m1000, tips) + for x in range(2): + m1000.aspirate(180, TL_samples_m[t].bottom(2)) + m1000.dispense(180, TL_samples_m[t].top(-5)) + m1000.aspirate(200, TL_samples_m[t].bottom(3)) + m1000.dispense(m1000.current_volume, samples_m[t].top()) + m1000.air_gap(10) + m1000.drop_tip() if not dry_run else m1000.return_tip() + + def AL_lysis(vol, source): + ctx.comment("-----Transferring AL to samples-----") + num_transfers = math.ceil(vol / 980) + tiptrack(m1000, tips) + for i in range(num_cols): + if num_cols >= 5: + if i == 0: + height = 10 + else: + height = 1 + else: + height = 1 + src = source + tvol = vol / num_transfers + for t in range(num_transfers): + m1000.aspirate(tvol, src.bottom(height)) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, samples_m[i].top()) + + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + mixing(samples_m[i], m1000, tvol - 50, reps=10 if not dry_run else 1) + m1000.drop_tip() if not dry_run else m1000.return_tip() + + ctx.comment("-----Mixing in AL Buffer-----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=15 if not dry_run else 0.25, msg="Shake at 2000 rpm for 4 minutes.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 15 minutes.") + + # ctx.pause("Add 5ul RNAse per sample now. Mix and incubate at RT for 2 minutes") + + def bind(vol): + """ + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead bining. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + ctx.comment("-----Beginning Bind-----") + tiptrack(m1000, tips) + for i, well in enumerate(samples_m): + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + source = binding_buffer[i // 3] # spread across 2 wells of reservoir + if i == 0 or i == 3: + reps = 6 if not dry_run else 1 + else: + reps = 1 + ctx.comment("-----Mixing Beads in Reservoir-----") + bead_mixing(source, m1000, vol_per_trans, reps=reps) + # Transfer beads and binding from source to H-S plate + for t in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, source.top()) + m1000.transfer(vol_per_trans, source, well.top(), air_gap=20, new_tip="never") + if t < num_trans - 1: + m1000.air_gap(20) + m1000.blow_out(well.top(-2)) + m1000.air_gap(10) + + ctx.comment("-----Mixing Beads in Plate-----") + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + bead_mixing(samples_m[i], m1000, starting_vol, reps=5 if not dry_run else 1) + m1000.drop_tip() if not dry_run else m1000.return_tip() + + ctx.comment("-----Mixing beads, bind and sample on plate-----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1800) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Shake at 1800 rpm for 10 minutes.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 1800 rpm for 10 minutes.") + + # Transfer from H-S plate to Magdeck plate + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for bindi in np.arange(settling_time + 1, 0, -0.5): # Settling time delay with countdown timer + ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") + + # remove initial supernatant + remove_supernatant(vol + starting_vol) + # Move plate from Magnet to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def wash(vol, source): + + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + whichwash = 1 + if source == wash2: + whichwash = 2 + if source == wash3: + whichwash = 3 + + ctx.comment("-----Beginning Wash #" + str(whichwash) + "-----") + + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + tiptrack(m1000, tips) + for i, m in enumerate(samples_m): + src = source[i // 2] # spread across 3 wells in reservoir + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.transfer(vol_per_trans, src, m.top(), air_gap=20, new_tip="never") + + m1000.drop_tip() if not dry_run else m1000.return_tip() + + ctx.comment("-----Mixing Wash Buffer and Sample-----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please wait 5 minutes while wash buffer shakes on H-S") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 2000 rpm.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for washi in np.arange(settling_time, 0, -0.5): # settling time timer for washes + ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in wash " + str(whichwash) + " incubation.") + + remove_supernatant(vol) + if source == wash1 or source == wash2: + # Move plate from Magnet to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + if dry_run: + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def elute(vol): + ctx.comment("-----Beginning Elution Steps-----") + tiptrack(m1000, tips) + for i, m in enumerate(samples_m): + src = elution_one[i // 3] + m1000.aspirate(e_one_vol, src) + m1000.air_gap(20) + m1000.dispense(m1000.current_volume, m.top(-3)) + m1000.blow_out() + + m1000.drop_tip() if not dry_run else m1000.return_tip() + + if num_cols == 1: + secs = 25 + else: + secs = 15 + + ctx.delay(seconds=secs) + + remove_supernatant(400) + # Move plate from Magnet to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + tiptrack(m1000, tips) + m1000.flow_rate.aspirate = 20 + for i, m in enumerate(samples_m): + m1000.aspirate(vol, elution_samples_m[i].bottom(0.1)) + m1000.air_gap(20) + m1000.dispense(m1000.current_volume, m.top(-3)) + # mixing(m,m1000,90,reps=8 if not dry_run else 1) + + m1000.flow_rate.aspirate = 300 + + m1000.drop_tip() if not dry_run else m1000.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Shake on H-S for 5 minutes at 2000 rpm.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 5 minutes.") + + # Transfer back to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for elutei in np.arange(settling_time, 0, -0.5): + ctx.delay(minutes=0.5, msg="Incubating on MagDeck for " + str(elutei) + " more minutes.") + + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + tiptrack(m1000, tips) + m1000.flow_rate.dispense = 100 + m1000.flow_rate.aspirate = 150 + m1000.transfer(vol, m.bottom(0.1), e.bottom(5), air_gap=20, new_tip="never") + m1000.blow_out(e.top(-2)) + m1000.air_gap(20) + m1000.drop_tip() if not dry_run else m1000.return_tip() + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + TL_lysis(TL_total_vol, TL) + AL_lysis(AL_vol, AL) + bind(binding_buffer_vol) + wash(wash1_vol, wash1) + if not dry_run: + wash(wash2_vol, wash2) + wash(wash3_vol, wash3) + temp.set_temperature(55) + elute(elution_vol) diff --git a/app-testing/files/protocols/pl_Omega_HDQ_DNA_Cells_Flex_96_channel.py b/app-testing/files/protocols/pl_Omega_HDQ_DNA_Cells_Flex_96_channel.py new file mode 100644 index 00000000000..ec66c38186d --- /dev/null +++ b/app-testing/files/protocols/pl_Omega_HDQ_DNA_Cells_Flex_96_channel.py @@ -0,0 +1,444 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"dry_run":false,"temp_mod":true,"heater_shaker":true,"tip_mixing":false,"wash_vol":600,"AL_vol":230,"sample_vol":180,"bind_vol":320,"elution_vol":100,"protocol_filename":"Omega_HDQ_DNA_Cells-Flex_96_channel"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +import json +import math +from opentrons import types +import numpy as np + +metadata = { + "author": "Zach Galluzzo ", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + trash_chute = False # If false, waste bin loaded in D3, if True, trash chute loaded there + USE_GRIPPER = True + dry_run = False + temp_mod = True + heater_shaker = True + tip_mixing = False + + wash_vol = 600 + AL_vol = 230 + bind_vol = 320 + sample_vol = 180 + elution_vol = 100 + + try: + [trash_chute, USE_GRIPPER, dry_run, temp_mod, heater_shaker, tip_mixing, wash_vol, sample_vol, bind_vol, AL_vol, elution_vol] = ( + get_values( # noqa: F821 + "trash_chute", + "USE_GRIPPER", + "dry_run", + "temp_mod", + "heater_shaker", + "tip_mixing", + "wash_vol", + "sample_vol", + "bind_vol", + "AL_vol", + "elution_vol", + ) + ) + + except NameError: + pass + + # Just to be safe + if heater_shaker: + tip_mixing = False + + # Same for all HDQ Extractions + deepwell_type = "nest_96_wellplate_2ml_deep" + if not dry_run: + settling_time = 2 + num_washes = 3 + if dry_run: + settling_time = 0.5 + num_washes = 1 + bead_vol = PK_vol = 20 + inc_temp = 55 + AL_total_vol = AL_vol + PK_vol + binding_buffer_vol = bead_vol + bind_vol + starting_vol = AL_total_vol + sample_vol + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Sample Plate") + + samples_m = sample_plate.wells()[0] + + # NOTE: MAG BLOCK will be on slot 6 + if temp_mod: + temp = ctx.load_module("temperature module gen2", "A3") + tempblock = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = tempblock.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate/ Reservoir") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate/ Reservoir") + + magblock = ctx.load_module("magneticBlockV1", "C1") + waste = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste").wells()[0].top() + + #'#008000','#A52A2A','#00FFFF','#0000FF','#800080','#ADD8E6','#FF0000','#FFFF00','#FF00FF','#00008B','#7FFFD4','#FFC0CB','#FFA500','#00FF00','#C0C0C0' + + lysis_reservoir = ctx.load_labware(deepwell_type, "D2", "Lysis reservoir") + lysis_res = lysis_reservoir.wells()[0] + lysis_buffer = ctx.define_liquid(name="Lysis Buffer", description="AL Buffer", display_color="#008000") + for well in lysis_reservoir.wells(): + well.load_liquid(liquid=lysis_buffer, volume=AL_vol + 92) + + PK_buffer = ctx.define_liquid(name="PK Buffer", description="Proteinase K", display_color="#008000") + for well in lysis_reservoir.wells(): + well.load_liquid(liquid=PK_buffer, volume=PK_vol + 8) + + bind_reservoir = ctx.load_labware(deepwell_type, "C2", "Beads and binding reservoir") + bind_res = bind_reservoir.wells()[0] + bind_buffer = ctx.define_liquid(name="Binding Buffer", description="Binding Buffer", display_color="#A52A2A") + for well in bind_reservoir.wells(): + well.load_liquid(liquid=bind_buffer, volume=bind_vol + 91.5) + + bead_buffer = ctx.define_liquid(name="Magnetic Beads", description="Magnetic Beads", display_color="#A52A2A") + for well in bind_reservoir.wells(): + well.load_liquid(liquid=bead_buffer, volume=bead_vol + 8.5) + + wash1_reservoir = ctx.load_labware(deepwell_type, "C3", "Wash 1 reservoir") + wash1_res = wash1_reservoir.wells()[0] + wash1_buffer = ctx.define_liquid(name="Wash 1 and 2 Buffer", description="VHB Buffer", display_color="#00FFFF") + for well in wash1_reservoir.wells(): + well.load_liquid(liquid=wash1_buffer, volume=(wash_vol * 2) + 100) + + wash2_reservoir = ctx.load_labware(deepwell_type, "B1", "Wash 2 reservoir") + wash2_res = wash2_reservoir.wells()[0] + wash2_buffer = ctx.define_liquid(name="Wash 3 Buffer", description="SPM Buffer", display_color="#0000FF") + for well in wash2_reservoir.wells(): + well.load_liquid(liquid=wash2_buffer, volume=wash_vol + 100) + + elution_res = elutionplate.wells()[0] + elution_buffer = ctx.define_liquid(name="Elution Buffer", description="Elution Buffer", display_color="#800080") + for well in elutionplate.wells(): + well.load_liquid(liquid=elution_buffer, volume=elution_vol + 5) + + samples = ctx.define_liquid(name="Samples", description="Samples in PBS", display_color="#ADD8E6") + for well in sample_plate.wells(): + well.load_liquid(liquid=samples, volume=sample_vol) + + # Load tips + tips = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + tips1 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + + # load 96 channel pipette + pip = ctx.load_instrument("flex_96channel_1000", mount="left") + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + pip.flow_rate.blow_out = 300 + + def resuspend_pellet(vol, plate, reps=3): + pip.flow_rate.aspirate = 200 + pip.flow_rate.dispense = 300 + + loc1 = plate.bottom().move(types.Point(x=1, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0.75, y=0.75, z=1)) + loc3 = plate.bottom().move(types.Point(x=0, y=1, z=1)) + loc4 = plate.bottom().move(types.Point(x=-0.75, y=0.75, z=1)) + loc5 = plate.bottom().move(types.Point(x=-1, y=0, z=1)) + loc6 = plate.bottom().move(types.Point(x=-0.75, y=0 - 0.75, z=1)) + loc7 = plate.bottom().move(types.Point(x=0, y=-1, z=1)) + loc8 = plate.bottom().move(types.Point(x=0.75, y=-0.75, z=1)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc2) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc3) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc4) + pip.dispense(mixvol, loc4) + pip.aspirate(mixvol, loc5) + pip.dispense(mixvol, loc5) + pip.aspirate(mixvol, loc6) + pip.dispense(mixvol, loc6) + pip.aspirate(mixvol, loc7) + pip.dispense(mixvol, loc7) + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + def bead_mix(vol, plate, reps=5): + pip.flow_rate.aspirate = 200 + pip.flow_rate.dispense = 300 + + loc1 = plate.bottom().move(types.Point(x=0, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0, y=0, z=8)) + loc3 = plate.bottom().move(types.Point(x=0, y=0, z=16)) + loc4 = plate.bottom().move(types.Point(x=0, y=0, z=24)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc4) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + # Just in case + if heater_shaker: + h_s.close_labware_latch() + + # Start Protocol + + # Transfer and mix lysis + pip.pick_up_tip(tips) + pip.aspirate(AL_total_vol, lysis_res) + pip.dispense(AL_total_vol, samples_m) + resuspend_pellet(400, samples_m, reps=4 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + # Mix, then heat + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1800) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please wait 10 minutes to allow for proper lysis mixing.") + if not dry_run: + h_s.set_and_wait_for_temperature(55) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please allow another 10 minutes of 55C incubation to complete lysis.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + ctx.comment( + "This step is usually performed at 55C for the second 10 minutes, if results dont look right, look into our heater-shaker." + ) + for x in range(3): + bead_mix(AL_vol, samples_m, reps=10) + ctx.delay(minutes=2) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 10 minutes at 1800 rpm, then increase temp to 55C for the second 10 minutes.") + + # Transfer and mix bind&beads + pip.pick_up_tip(tips) + bead_mix(binding_buffer_vol, bind_res, reps=4 if not dry_run else 1) + pip.aspirate(binding_buffer_vol, bind_res) + pip.dispense(binding_buffer_vol, samples_m) + bead_mix(binding_buffer_vol + starting_vol, samples_m, reps=4 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + pip.home() + + # Shake for binding incubation + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please allow 10 minutes for the beads to bind the DNA.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + for x in range(2): + bead_mix(binding_buffer_vol, samples_m, reps=8) + ctx.delay(minutes=2) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 10 minutes at 1800 rpm.") + + if heater_shaker: + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware( + sample_plate, + magblock, + use_gripper=USE_GRIPPER, + ) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay( + minutes=settling_time, + msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.", + ) + + # Remove Supernatant and move off magnet + pip.pick_up_tip(tips) + pip.aspirate(1000, samples_m.bottom(0.3)) + pip.dispense(1000, waste) + if starting_vol + binding_buffer_vol > 1000: + pip.aspirate(1000, samples_m.bottom(0.1)) + pip.dispense(1000, waste) + pip.return_tip() + + # Transfer plate from magnet to H/S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware( + sample_plate, + h_s_adapter if heater_shaker else "D1", + use_gripper=USE_GRIPPER, + ) + if heater_shaker: + h_s.close_labware_latch() + + # Washes + for i in range(num_washes if not dry_run else 1): + if i == 0 or i == 1: + wash_res = wash1_res + else: + wash_res = wash2_res + + pip.pick_up_tip(tips) + pip.aspirate(wash_vol, wash_res) + pip.dispense(wash_vol, samples_m) + if not tip_mixing: + pip.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=5 if not dry_run else 0.25) + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(wash_vol, samples_m, reps=12) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 1800 rpm.") + + # Transfer plate to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware( + sample_plate, + magblock, + use_gripper=USE_GRIPPER, + ) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay( + minutes=settling_time, + msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.", + ) + + # Remove Supernatant and move off magnet + pip.pick_up_tip(tips) + pip.aspirate(1000, samples_m.bottom(0.3)) + pip.dispense(1000, bind_res.top()) + if wash_vol > 1000: + pip.aspirate(1000, samples_m.bottom(0.3)) + pip.dispense(1000, bind_res.top()) + pip.return_tip() + + # Transfer plate from magnet to H/S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware( + sample_plate, + h_s_adapter if heater_shaker else "D1", + use_gripper=USE_GRIPPER, + ) + if heater_shaker: + h_s.close_labware_latch() + + # Dry beads + if dry_run: + drybeads = 0.5 + else: + drybeads = 10 + # Number of minutes you want to dry for + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + + # Elution + pip.pick_up_tip(tips1) + pip.aspirate(elution_vol, elution_res) + pip.dispense(elution_vol, samples_m) + resuspend_pellet(elution_vol, samples_m, reps=3 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + pip.home() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay( + minutes=5 if not dry_run else 0.25, + msg="Please wait 5 minutes to allow dna to elute from beads.", + ) + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(elution_vol, samples_m, reps=12) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 2000 rpm.") + + # Transfer plate to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware( + sample_plate, + magblock, + use_gripper=USE_GRIPPER, + ) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay( + minutes=settling_time, + msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.", + ) + + pip.pick_up_tip(tips1) + pip.aspirate(elution_vol, samples_m) + pip.dispense(elution_vol, elutionplate.wells()[0]) + pip.return_tip() + + pip.home() diff --git a/app-testing/files/protocols/pl_Omega_HDQ_DNA_Cells_Flex_multi.py b/app-testing/files/protocols/pl_Omega_HDQ_DNA_Cells_Flex_multi.py new file mode 100644 index 00000000000..039d6046cc7 --- /dev/null +++ b/app-testing/files/protocols/pl_Omega_HDQ_DNA_Cells_Flex_multi.py @@ -0,0 +1,613 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"dry_run":false,"mount":"left","temp_mod":true,"heater_shaker":true,"res_type":"nest_12_reservoir_15ml","num_samples":8,"wash1_vol":600,"wash2_vol":600,"wash3_vol":600,"AL_vol":230,"sample_vol":180,"bind_vol":320,"elution_vol":100,"protocol_filename":"Omega_HDQ_DNA_Cells-Flex_multi"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +import json +import math +from opentrons import types +import numpy as np + +metadata = { + "author": "Zach Galluzzo ", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + +""" +Here is where you can modify the magnetic module engage height: +""" +whichwash = 1 +sample_max = 48 +tip1k = 0 +drop_count = 0 +waste_vol = 0 + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + trash_chute = False # If this is true, trash chute is loaded in D3, otherwise trash bin is loaded there + USE_GRIPPER = True + dry_run = False + mount = "left" + res_type = "nest_12_reservoir_22ml" + temp_mod = True # True or false if you have a temp mod loaded on deck with the elution plate + heater_shaker = True + + num_samples = 48 + wash1_vol = 600 + wash2_vol = 600 + wash3_vol = 600 + AL_vol = 230 + sample_vol = 180 + bind_vol = 320 + elution_vol = 100 + + try: + [ + res_type, + temp_mod, + trash_chute, + USE_GRIPPER, + dry_run, + mount, + num_samples, + heater_shaker, + wash1_vol, + wash2_vol, + wash3_vol, + AL_vol, + sample_vol, + bind_vol, + elution_vol, + ] = get_values( # noqa: F821 + "res_type", + "temp_mod", + "trash_chute", + "USE_GRIPPER", + "dry_run", + "mount", + "num_samples", + "heater_shaker", + "wash1_vol", + "wash2_vol", + "wash3_vol", + "AL_vol", + "sample_vol", + "bind_vol", + "elution_vol", + ) + + except NameError: + pass + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + res_type = "nest_12_reservoir_15ml" + if not dry_run: + settling_time = 2 + else: + settling_time = 0.25 + PK_vol = bead_vol = 20 + AL_total_vol = AL_vol + PK_vol + starting_vol = AL_vol + sample_vol + binding_buffer_vol = bind_vol + bead_vol + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") + h_s.close_labware_latch() + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Sample Plate") + + if temp_mod: + temp = ctx.load_module("temperature module gen2", "A3") + temp_block = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = temp_block.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate") + + magnetic_block = ctx.load_module("magneticBlockV1", "C1") + waste = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste").wells()[0].top() + res1 = ctx.load_labware(res_type, "D2", "reagent reservoir 1") + num_cols = math.ceil(num_samples / 8) + + # Load tips and combine all similar boxes + tips1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "Tips 1") + tips1001 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "Tips 2") + tips1002 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B1", "Tips 3") + tips = [*tips1000.wells()[8 * (num_cols) : 96], *tips1001.wells(), *tips1002.wells()] + tips_sn = tips1000.wells()[: 8 * (num_cols)] + + # load instruments + m1000 = ctx.load_instrument("flex_8channel_1000", mount) + + """ + Here is where you can define the locations of your reagents. + """ + binding_buffer = res1.wells()[:2] + AL = res1.wells()[2] + wash1 = res1.wells()[3:6] + wash2 = res1.wells()[6:9] + wash3 = res1.wells()[9:] + + samples_m = sample_plate.rows()[0][:num_cols] + elution_samples_m = elutionplate.rows()[0][:num_cols] + + colors = [ + "#008000", + "#008000", + "#A52A2A", + "#A52A2A", + "#00FFFF", + "#0000FF", + "#800080", + "#ADD8E6", + "#FF0000", + "#FFFF00", + "#FF00FF", + "#00008B", + "#7FFFD4", + "#FFC0CB", + "#FFA500", + "#00FF00", + "#C0C0C0", + ] + + # Begin with assigning plate wells before reservoir wells + samps = ctx.define_liquid(name="Samples", description="Samples", display_color="#00FF00") + elution_samps = ctx.define_liquid(name="Elution Buffer", description="Elution Buffer", display_color="#FFA500") + + for well in sample_plate.wells()[:num_samples]: + well.load_liquid(liquid=samps, volume=sample_vol) + + for well in elutionplate.wells()[:num_samples]: + well.load_liquid(liquid=elution_samps, volume=elution_vol) + + # Start defining reservoir wells + locations = [AL, AL, binding_buffer, binding_buffer, wash1, wash2, wash3] + vols = [AL_vol, PK_vol, bead_vol, bind_vol, wash1_vol, wash2_vol, wash3_vol] + liquids = ["AL Lysis", "PK", "Beads", "Binding", "Wash 1", "Wash 2", "Wash 3"] + + delete = len(colors) - len(liquids) + + if delete >= 1: + for i in range(delete): + colors.pop(-1) + + def liquids_(liq, location, color, vol): + sampnum = 8 * (math.ceil(num_samples / 8)) + """ + Takes an individual liquid at a time and adds the color to the well + in the description. + """ + # Volume Calculation + if liq == "PK": + extra_samples = math.ceil(1500 / AL_vol) + + elif liq == "Beads": + extra_samples = math.ceil(1500 / bind_vol) + + else: + extra_samples = math.ceil(1500 / vol) + + # Defining and assigning liquids to wells + if isinstance(location, list): + limit = sample_max / len(location) # Calculates samples/ res well + iterations = math.ceil(sampnum / limit) + left = sampnum - limit + while left > limit: + left = left - limit + if left > 0: + last_iteration_samp_num = left + elif left < 0: + last_iteration_samp_num = sampnum + else: + last_iteration_samp_num = limit + + samples_per_well = [] + + for i in range(iterations): + # append the left over on the last iteration + if i == (iterations - 1): + samples_per_well.append(last_iteration_samp_num) + else: + samples_per_well.append(limit) + + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + for sample, well in zip(samples_per_well, location[: len(samples_per_well)]): + v = vol * (sample + extra_samples) + well.load_liquid(liquid=liq, volume=v) + else: + v = vol * (sampnum + extra_samples) + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + location.load_liquid(liquid=liq, volume=v) + + for x, (ll, l, c, v) in enumerate(zip(liquids, locations, colors, vols)): + liquids_(ll, l, c, v) + + m1000.flow_rate.aspirate = 300 + m1000.flow_rate.dispense = 300 + m1000.flow_rate.blow_out = 300 + + def tiptrack(pip, tipbox): + global tip1k + global tip200 + global drop_count + if tipbox == tips: + m1000.pick_up_tip(tipbox[int(tip1k)]) + tip1k = tip1k + 8 + drop_count = drop_count + 8 + if drop_count >= 150: + drop_count = 0 + ctx.pause("Please empty the waste bin of all the tips before continuing.") + + def blink(): + for i in range(3): + ctx.set_rail_lights(True) + ctx.delay(minutes=0.01666667) + ctx.set_rail_lights(False) + ctx.delay(minutes=0.01666667) + + def remove_supernatant(vol): + ctx.comment("-----Removing Supernatant-----") + m1000.flow_rate.aspirate = 150 + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + + def _waste_track(vol): + global waste_vol + waste_vol = waste_vol + (vol * 8) + if waste_vol >= 185000: + m1000.home() + blink() + ctx.pause("Please empty liquid waste before resuming.") + waste_vol = 0 + + for i, m in enumerate(samples_m): + m1000.pick_up_tip(tips_sn[8 * i]) + loc = m.bottom(0.5) + for _ in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, m.top()) + m1000.move_to(m.center()) + m1000.transfer(vol_per_trans, loc, waste, new_tip="never", air_gap=20) + m1000.blow_out(waste) + m1000.air_gap(20) + m1000.drop_tip(tips_sn[8 * i]) + m1000.flow_rate.aspirate = 300 + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def bead_mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top().move(types.Point(x=0, y=0, z=5)) + aspbot = well.bottom().move(types.Point(x=0, y=2, z=1)) + asptop = well.bottom().move(types.Point(x=0, y=-2, z=2.5)) + disbot = well.bottom().move(types.Point(x=0, y=1.5, z=3)) + distop = well.top().move(types.Point(x=0, y=1.5, z=0)) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, aspbot) + pip.dispense(vol, distop) + pip.aspirate(vol, asptop) + pip.dispense(vol, disbot) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, aspbot) + pip.dispense(vol, aspbot) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(1) + disp = well.top(-8) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def A_lysis(vol, source): + ctx.comment("-----Mixing then transferring AL buffer-----") + num_transfers = math.ceil(vol / 980) + tiptrack(m1000, tips) + for i in range(num_cols): + if num_cols >= 5: + if i == 0: + height = 10 + else: + height = 1 + else: + height = 1 + src = source + tvol = vol / num_transfers + for t in range(num_transfers): + if i == 0 and t == 0: + for _ in range(3): + m1000.aspirate(tvol, src.bottom(1)) + m1000.dispense(tvol, src.bottom(4)) + m1000.aspirate(tvol, src.bottom(height)) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, samples_m[i].top()) + m1000.air_gap(20) + + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + mixing(samples_m[i], m1000, tvol - 40, reps=10 if not dry_run else 1) # vol is 250 AL + 180 sample + m1000.air_gap(20) + m1000.drop_tip() if not dry_run else m1000.return_tip() + + ctx.comment("-----Mixing then Heating AL and Sample-----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=15 if not dry_run else 0.25, msg="Shake at 2000 rpm for 5 minutes.") + if not dry_run: + h_s.set_and_wait_for_temperature(55) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Incubating at 55C 2000 rpm for 10 minutes.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause( + msg="Place on shaker for 15 minutes at 2000 rpm. Then increase temperature to 55C and continue shaking for 10 minutes." + ) + + # ctx.pause("Add 5ul RNAse per sample now. Mix and incubate at RT for 2 minutes") + + def bind(vol): + """ + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead bining. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + ctx.comment("-----Beginning Bind Steps-----") + tiptrack(m1000, tips) + for i, well in enumerate(samples_m): + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + source = binding_buffer[i // 3] + if i == 0: + reps = 6 if not dry_run else 1 + else: + reps = 1 + ctx.comment("-----Mixing Beads in Reservoir-----") + bead_mixing(source, m1000, vol_per_trans, reps=reps if not dry_run else 1) + # Transfer beads and binding from source to H-S plate + for t in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, source.top()) + m1000.transfer(vol_per_trans, source, well.top(), air_gap=20, new_tip="never") + if t < num_trans - 1: + m1000.air_gap(20) + + ctx.comment("-----Mixing Beads in Plate-----") + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + mixing(samples_m[i], m1000, vol + starting_vol, reps=10 if not dry_run else 1) + m1000.drop_tip() if not dry_run else m1000.return_tip() + + ctx.comment("-----Incubating Beads and Bind on H-S-----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1800) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Shake at 1800 rpm for 10 minutes.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker for 10 minutes at 1800 rpm.") + + # Transfer from H-S plate to Magdeck plate + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magnetic_block, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for bindi in np.arange(settling_time + 1, 0, -0.5): # Settling time delay with countdown timer + ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") + + # remove initial supernatant + remove_supernatant(vol + starting_vol) + + def wash(vol, source): + + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + whichwash = 1 + if source == wash2: + whichwash = 2 + if source == wash3: + whichwash = 3 + + ctx.comment("-----Beginning Wash #" + str(whichwash) + "-----") + + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + tiptrack(m1000, tips) + for i, m in enumerate(samples_m): + src = source[i // 2] + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.transfer(vol_per_trans, src, m.top(), air_gap=20, new_tip="never") + m1000.drop_tip() if not dry_run else m1000.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=5 if not dry_run else 0.25) + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 2000 rpm.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magnetic_block, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for washi in np.arange(settling_time, 0, -0.5): # settling time timer for washes + ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in wash " + str(whichwash) + " incubation.") + + remove_supernatant(vol) + + def elute(vol): + ctx.comment("-----Beginning Elution Steps-----") + tiptrack(m1000, tips) + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + m1000.flow_rate.aspirate = 25 + m1000.aspirate(vol, e.bottom(0.5)) + m1000.air_gap(20) + m1000.dispense(m1000.current_volume, m.top()) + m1000.flow_rate.aspirate = 150 + m1000.drop_tip() if not dry_run else m1000.return_tip() + + """ + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + for x in range(4 if not dry_run else 1): + m1000.aspirate(elution_vol*.9,m.bottom(1)) + m1000.dispense(m1000.current_volume,m.bottom(20)) + if i == 3: + m1000.flow_rate.aspirate = 50 + m1000.flow_rate.dispense = 20 + m1000.aspirate(elution_vol*.9,m.bottom(1)) + m1000.dispense(m1000.current_volume,m.bottom(1)) + m1000.flow_rate.aspirate = 300 + m1000.flow_rate.dispense = 300 + m1000.drop_tip() if not dry_run else m1000.return_tip() + """ + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2200) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Shake on H-S for 5 minutes at 2000 rpm.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 2200 rpm for 5 minutes.") + + # Transfer back to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magnetic_block, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for elutei in np.arange(settling_time, 0, -0.5): + ctx.delay(minutes=0.5, msg="Incubating on MagDeck for " + str(elutei) + " more minutes.") + + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + tiptrack(m1000, tips) + m1000.flow_rate.dispense = 100 + m1000.flow_rate.aspirate = 150 + m1000.transfer(vol, m.bottom(0.15), e.bottom(5), air_gap=20, new_tip="never") + m1000.blow_out(e.top(-2)) + m1000.air_gap(20) + m1000.drop_tip() if not dry_run else m1000.return_tip() + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + A_lysis(AL_total_vol, AL) + bind(binding_buffer_vol) + wash(wash1_vol, wash1) + + if not dry_run: + drybeads = 10 # Number of minutes you want to dry for + wash(wash2_vol, wash2) + wash(wash3_vol, wash3) + else: + drybeads = 0.5 + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + elute(elution_vol) diff --git a/app-testing/files/protocols/pl_QIASeq_FX_48x_v8.py b/app-testing/files/protocols/pl_QIASeq_FX_48x_v8.py new file mode 100644 index 00000000000..50a467e9bd5 --- /dev/null +++ b/app-testing/files/protocols/pl_QIASeq_FX_48x_v8.py @@ -0,0 +1,2299 @@ +from opentrons import protocol_api +from opentrons import types +import math + +metadata = { + "protocolName": "QIASeq FX 48x v8", + "author": "Opentrons ", + "source": "Protocol Library", +} +requirements = { + "robotType": "Flex", + "apiLevel": "2.18", +} + + +def add_parameters(parameters): + # ======================== RUNTIME PARAMETERS ======================== + parameters.add_bool(display_name="Dry Run", variable_name="DRYRUN", default=False, description="Whether to perform a dry run or not.") + parameters.add_int( + display_name="Sample Column count", + variable_name="COLUMNS", + default=3, + minimum=1, + maximum=6, + description="How many sample columns to process.", + ) + parameters.add_int( + display_name="Fragmentation Time (Min)", + variable_name="FRAGTIME", + default=15, + minimum=10, + maximum=30, + description="Length of Fragmentation Incubation.", + ) + parameters.add_int( + display_name="PCR Cycles", + variable_name="PCRCYCLES", + default=4, + minimum=1, + maximum=12, + description="How many PCR Cycles to for amplification.", + ) + parameters.add_bool( + display_name="Tip Mixing", + variable_name="TIP_MIX", + default=False, + description="Whether or not to use Tip Mixing instead of the Heatershaker", + ) + parameters.add_bool( + display_name="On Deck Thermo Module", + variable_name="ONDECK_THERMO", + default=True, + description="Whether or not to have an on deck Thermocycler", + ) + parameters.add_str( + display_name="Tip Setting", + variable_name="TIP_SETTING", + default="Single Tip Use", + description="Tip Settings", + choices=[ + {"display_name": "Reusing Tips", "value": "Reusing Tips"}, + {"display_name": "Single Tip Use", "value": "Single Tip Use"}, + {"display_name": "None", "value": "None"}, + ], + ) + + +def run(protocol: protocol_api.ProtocolContext): + # ======================== DOWNLOADED PARAMETERS ======================== + global USE_GRIPPER # T/F Whether or not Using the Gripper + global STP_50_TIPS # T/F Whether or not there are p50 Single Tip Pickups + global STP_200_TIPS # T/F Whether or not there are p200 Single Tip Pickups + global REUSE_ANY_50_TIPS # T/F Whether or not Reusing any p50 + global REUSE_ANY_200_TIPS # T/F Whether or not Reusing any p200 + global TIP_TRASH # T/F whether or not the Tips are Returned + global COLUMNS # Number of Columns of Samples + global PLATE_STACKED # Number of Plates Stacked in Stacked Position + global p50_TIPS # Number of p50 tips currently available + global p200_TIPS # Number of p200 tips currently available + global p50_RACK_COUNT # Number of current total p50 racks + global p200_RACK_COUNT # Number of current total p200 racks + global tiprack_200_STP # Tiprack for p200 Single Tip Pickup + global tiprack_200_STR # Tiprack for p200 Single Tip Return + global tiprack_50_STP # Tiprack for p50 Single Tip Pickup + global tiprack_50_STR # Tiprack for p50 Single Tip Return + global tiprack_50_R # Tiprack for p50 Reuse + global tiprack_200_R1 # Tiprack for p200 Reuse #1 + global tiprack_200_R2 # Tiprack for p200 Reuse #2 + global WASTEVOL # Number - Total volume of Discarded Liquid Waste + global ETOHVOL # Number - Total volume of Available EtOH + # =================== LOADING THE RUNTIME PARAMETERS ==================== + + DRYRUN = protocol.params.DRYRUN + COLUMNS = protocol.params.COLUMNS + FRAGTIME = protocol.params.FRAGTIME + PCRCYCLES = protocol.params.PCRCYCLES + TIP_MIX = protocol.params.TIP_MIX + ONDECK_THERMO = protocol.params.ONDECK_THERMO + TIP_SETTING = protocol.params.TIP_SETTING + + # ================================================================================================= + # ====================================== ADVANCED PARAMETERS ====================================== + # ================================================================================================= + # -------PROTOCOL STEP------- + STEP_FXENZ = True # Set to 0 to skip block of commands + STEP_LIG = True # Set to 0 to skip block of commands + STEP_CLEANUP_1 = True # Set to 0 to skip block of commands + STEP_CLEANUP_2 = True # Set to 0 to skip block of commands + STEP_PCR = True # Set to 0 to skip block of commands + STEP_CLEANUP_3 = True # Set to 0 to skip block of commands + # --------------------------- + # This notifies the user that for 5-6 columns (from more than 32 samples up to 48 samples) it requires Tip reusing in order to remain walkaway. + # This setting will override any Runtime parameter, and also pauses to notify the user. So if the user enters 6 columns with Single Tip Use, it will pause and warn that it has to change to Reusing tips in order to remain walkaway. + # Note that if omitting steps (i.e. skipping the last cleanup step) it is possible to do single use tips, but may vary on case by case basis. + # Note that it is also possible to use advanced settings to include pauses that requires user intervention to replenish tipracks, making allowing a run of single Use Tips. + AllSteps = [STEP_FXENZ, STEP_LIG, STEP_CLEANUP_1, STEP_CLEANUP_2, STEP_PCR, STEP_CLEANUP_3] + if COLUMNS >= 4 and all(AllSteps) == True: + protocol.pause("MUST REUSE TIPS") + TIP_SETTING = "Reusing Tips" + + TIP_TRASH = True # Default True | True = Used tips go in Trash, False = Used tips go back into rack + DEACTIVATE_TEMP = True # Default True | True = Temp and / or Thermocycler deactivate at end of run, False = remain on, such as leaving at 4 degrees + TRASH_POSITION = "CHUTE" # Default 'CHUTE' | 'BIN' or 'CHUTE' + ONDECK_HEATERSHAKER = True # Default True | True = On Deck Heater Shaker, False = No heatershaker and increased tip mixing reps. + ONDECK_TEMP = True # Default True | True = On Deck Temperature module, False = No Temperature Module + USE_GRIPPER = True # Default True | True = Uses the FLEX Gripper, False = No Gripper Movement, protocol pauses and requires manual intervention. + HOTSWAP = False # Default False | True = Allows replenishing tipracks on the off deck positions so the protocol can continue, False = Won't, protocol will most likely have out of tip error message. + HOTSWAP_PAUSE = False # Default False | True = Protocol pauses for replenishing the offdeck tip racks or to continue, False = Protocol won't cause, user must add tipracks at their discretion. + SWAPOFFDECK = False # Default False | True = Protocol will use an empty deck position as a temprorary place to swap new and used tip racks between on and off deck, instead of discarding in the chute, False = won't, and used tipracks will go into the chute. Use True if there is deck space to spare and when doing troubleshooting so tips aren't being discarded with the tip racks. + CUSTOM_OFFSETS = False # Default False | True = use per instrument specific offsets, False = Don't use any offsets. This is for per instrument, per module gripper alignment adjustments that need fine tuning without gripper recalibration. + RES_TYPE_96x = False # Default False | True = use a 96x2ml deepwell for the Reagent Reservoir to keep tips compartmentalized, False = 12x15ml Reservoir. + ETOH_1_AirMultiDis = ( + False # Default False | When adding EtOH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + ) + REUSE_50_TIPS_RSB = False # Default False | Reusing p50 tips + REUSE_200_TIPS_ETOH_1 = False # Default False | Reusing p200 tips + STP_200_TIPS = False # Default False | Single Tip Pickup p200 tips + STP_50_TIPS = False # Default False | Single tip Pickup p50 tips + NOLABEL = True # Default False | True = Do no include Liquid Labeling, False = Liquid Labeling is included, adds additional lines to Protocol Step Preview at end of protocol. + REPORT = ( + False # Default False | True = Include Extra Comments in the Protocol Step Preview for troubleshooting, False = Do Not Include + ) + + # ============================== SETTINGS =============================== + if COLUMNS == 12: + BATCHREP = 3 # Do Final Cleanup in 3 batches of columns + if COLUMNS == 6: + BATCHREP = 2 # Do Final Cleanup in 2 batches of columns + if COLUMNS < 6: + BATCHREP = 1 # Do Final Cleanup all at once + if TRASH_POSITION == "BIN": + SWAPOFFDECK = True # Setting to Swap empty Tipracks to empty positions instead of dumping them + if TRASH_POSITION == "CHUTE": + SWAPOFFDECK = False # Setting to Swap empty Tipracks to empty positions instead of dumping them + if DRYRUN == True: + TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack + DEACTIVATE_TEMP = True # Whether or not to deactivate the heating and cooling modules after a run + REPORT = True # Whether or not to include Extra Comments for Debugging + if TIP_SETTING == "Reusing Tips": + RES_TYPE_96x = True # Type of Reservoir, if reusing tips or omitting rows, set True to use a 96x2ml deepwell + ETOH_1_AirMultiDis = True # When adding WASH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + REUSE_50_TIPS_RSB = True # Default False | Reusing p50 tips + REUSE_200_TIPS_ETOH_1 = True # Reusing p200 tips + if TIP_SETTING == "Single Tip Use": + RES_TYPE_96x = False # Type of Reservoir, if reusing tips or omitting rows, set True to use a 96x2ml deepwell + ETOH_1_AirMultiDis = False # When adding WASH to multiple columns, dispense above the wells and reuse tips (Tip Saving) + REUSE_50_TIPS_RSB = False # Default False | Reusing p50 tips + REUSE_200_TIPS_ETOH_1 = False # Reusing p200 tips + + # ======================== BACKGROUND PARAMETERS ======================== + p50_TIPS = 0 # Number of p50 tips currently available + p200_TIPS = 0 # Number of p50 tips currently available + RESETCOUNT = 0 # Number of times the protocol was paused to reset tips + p50_RACK_COUNT = 0 # Number of current total p50 racks + p200_RACK_COUNT = 0 # Number of current total p200 racks + WASTEVOL = 0 # Number - Total volume of Discarded Liquid Waste + ETOHVOL = 0 # Number - Total volume of Available EtOH + PLATE_STACKED = 0 # Number of Plates Stacked in Stacked Position + REUSE_50_TIPS_COUNT = 0 + REUSE_ANY_50_TIPS = False + if REUSE_50_TIPS_RSB == True: + REUSE_ANY_50_TIPS = True + REUSE_50_TIPS_COUNT += COLUMNS + REUSE_200_TIPS_COUNT = 0 + REUSE_ANY_200_TIPS = False + if REUSE_200_TIPS_ETOH_1 == True: + REUSE_ANY_200_TIPS = True + REUSE_200_TIPS_COUNT += COLUMNS + + # =============================== PIPETTE =============================== + p1000 = protocol.load_instrument("flex_8channel_1000", "left") + p50 = protocol.load_instrument("flex_8channel_50", "right") + p1000_flow_rate_aspirate_default = 200 + p1000_flow_rate_dispense_default = 200 + p1000_flow_rate_blow_out_default = 400 + p50_flow_rate_aspirate_default = 50 + p50_flow_rate_dispense_default = 50 + p50_flow_rate_blow_out_default = 100 + + # ================================ LISTS ================================ + p50_RACKS_PIPET = [] # Pipette List + p200_RACKS_PIPET = [] # Pipette List + AVAILABLE_POS_ONDECK = [] # List of Available Positions ON DECK + AVAILABLE_POS_OFFDECK = [] # List of Available Positions OFF DECK + RACKS_TO_DUMP = [] # List of Emptied Racks ON DECK + p50_RACKS_ONDECK = [] # List of P50 Racks ON DECK + p50_RACKS_OFFDECK = [] # List of P50 Racks OFF DECK + p50_RACKS_DROPPED = [] # List of P50 Racks DROPPED + p200_RACKS_ONDECK = [] # List of P200 Racks ON DECK + p200_RACKS_OFFDECK = [] # List of P200 Racks OFF DECK + p200_RACKS_DROPPED = [] # List of P200 Racks DROPPED + SWAPSPOT = [] # List of Next Available Position for SWAP + REUSE_50_TIPS = [] # List of Next Available Position for SWAP + p50_INITIALTIPS = [] # List of Next Available Position for SWAP + REUSE_200_TIPS_1 = [] # List of Next Available Position for SWAP + REUSE_200_TIPS_2 = [] # List of Next Available Position for SWAP + p200_INITIALTIPS = [] # List of Next Available Position for SWAP + + def DefinePosition(tiptype, position, status): + # A Function that is called for potential tip Rack Position. Rather than defining tipracks at the beginning this function is called for each potential tip rack position, values are passed + # to the function and the tip position is added to the appropriate list as, Single Tip Pickup (STP), Reusable Tips, of left as OPEN which can be filled with p50 or p200 as needed. + global STP_50_TIPS + global STP_200_TIPS + global COLUMNS + global REUSE_ANY_50_TIPS + global REUSE_ANY_200_TIPS + global p50_RACK_COUNT + global p200_RACK_COUNT + global tiprack_200_STP + global tiprack_200_STR + global tiprack_50_R + global tiprack_200_R1 + global tiprack_200_R2 + if status == "OPEN": + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "STP_200" and STP_200_TIPS == True: + tiprack_200_STP = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_STP") + for i in range(1, 13): + STP_200_list_x4.append(tiprack_200_STP[f"E{i}"]) + STP_200_list_x4.append(tiprack_200_STP[f"A{i}"]) + rows = ["H", "G", "F", "E", "D", "C", "B", "A"] + columns = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] + for col in columns: + for row in rows: + STP_200_list_x1.append(tiprack_200_STP[row + col]) + if status == "STR_200" and STP_200_TIPS == True: + tiprack_200_STR = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_STR") + for i in range(1, 13): + STR_200_list.append(tiprack_200_STR[f"A{i}"]) + STR_200_list.append(tiprack_200_STR[f"E{i}"]) + if status == "STP_50" and STP_50_TIPS == True: + tiprack_50_STP = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_STP") + for i in range(1, 13): + STP_50_list_x4.append(tiprack_50_STP[f"E{i}"]) + STP_50_list_x4.append(tiprack_50_STP[f"A{i}"]) + rows = ["H", "G", "F", "E", "D", "C", "B", "A"] + columns = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] + for col in columns: + for row in rows: + STP_50_list_x1.append(tiprack_50_STP[row + col]) + if status == "STR_50" and STP_50_TIPS == True: + tiprack_50_STR = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_STR") + for i in range(1, 13): + STR_50_list.append(tiprack_50_STR[f"A{i}"]) + STR_50_list.append(tiprack_50_STR[f"E{i}"]) + if status == "REUSE_50_TIPS" and REUSE_ANY_50_TIPS == True: + p50_RACK_COUNT += 1 + tiprack_50_R = protocol.load_labware("opentrons_flex_96_tiprack_50ul", position, f"tiprack_50_R") + protocol.comment(f"Adding tiprack_50_R") + for X in range(COLUMNS * 2): + REUSE_50_TIPS.append(tiprack_50_R.wells_by_name().get(f"A{X+1}", None)) + if COLUMNS * 2 != 12: + pass + else: + for X in range(COLUMNS * 2, 12): + p50_INITIALTIPS.append(tiprack_50_R.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_50_TIPS" and REUSE_ANY_50_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + if status == "REUSE_200_1TIPS" and REUSE_ANY_200_TIPS == True: + p200_RACK_COUNT += 1 + tiprack_200_R1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", position, f"tiprack_200_R1") + protocol.comment(f"Adding tiprack_200_R1") + for X in range(COLUMNS * 2): + REUSE_200_TIPS_1.append(tiprack_200_R1.wells_by_name().get(f"A{X+1}", None)) + if COLUMNS * 2 != 12: + pass + else: + for X in range(COLUMNS * 2, 12): + p200_INITIALTIPS.append(tiprack_200_R1.wells_by_name().get(f"A{X+1}", None)) + if status == "REUSE_200_1TIPS" and REUSE_ANY_200_TIPS == False: + if position[1:2] != "4": + AVAILABLE_POS_ONDECK.append(position) + if position[1:2] == "4": + AVAILABLE_POS_OFFDECK.append(position) + + def TipCheck(tiptype, tipuse, rep, tipset): + # A Function that is called replacing the Tip Pickup Step. Values are passed to the function such as tip type, reuse, and which rep (loop number) it is on. + # 1). Checks if there are 0 spaces available, if so, it clears the off deck positions allowing it to be refilled + # 2). Then, if its not Single Tip Pickup (which has its own separate list), if there are no positions available, and not a Reusing step, it adds a rack to either the On or Off Deck + # 3). If it is an Off Deck Position, it automatically starts the TipRackSwap Function, removing the next in line empry rack and swapping in the newly added Off Deck rack + global p50_TIPS + global p200_TIPS + global p50_RACK_COUNT + global p200_RACK_COUNT + global RESETCOUNT + global TIPDONEMODE + if len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) == 0: + if HOTSWAP == True: + for loop, X in enumerate(OFFDECK_LIST): + del protocol.deck[X] + if HOTSWAP_PAUSE == True: + protocol.pause("CLEARING OFFDECK POSITIONS") + else: + protocol.comment("CLEARING OFFDECK POSITIONS") + AVAILABLE_POS_OFFDECK.append(X) + if tiptype == 50 and tipuse != "STP": + if p50_TIPS == 0 and len(p50_INITIALTIPS) == 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + if len(p50_RACKS_ONDECK) == 0: + protocol.comment("FIRST RACK") + else: + protocol.comment("IS THERE AN EMPTY RACK?") + RACKS_TO_DUMP.append(p50_RACKS_ONDECK[0]) + p50_RACKS_ONDECK.pop(0) + p50_RACK_COUNT += 1 + p50_TIPS += 12 + protocol.comment(f"Adding tiprack_50_{p50_RACK_COUNT}") + if len(AVAILABLE_POS_ONDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_50ul", AVAILABLE_POS_ONDECK[0], f"tiprack_50_{p50_RACK_COUNT}" + ) + AVAILABLE_POS_ONDECK.pop(0) + p50_RACKS_ONDECK.append(addtiprack) + p50_RACKS_PIPET.append(addtiprack) + p50.tip_racks = p50_RACKS_PIPET + elif len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_50ul", AVAILABLE_POS_OFFDECK[0], f"tiprack_50_{p50_RACK_COUNT}" + ) + AVAILABLE_POS_OFFDECK.pop(0) + p50_RACKS_OFFDECK.append(addtiprack) + p50_RACKS_PIPET.append(addtiprack) + p50.tip_racks = p50_RACKS_PIPET + TipRackSwap(50) + if p50_TIPS == 0 and len(p50_INITIALTIPS) > 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + p50.pick_up_tip(p50_INITIALTIPS[0]) + p50_INITIALTIPS.pop(0) + if p50_TIPS > 0: + if tipuse == "REUSE" and REUSE_ANY_50_TIPS == True: + p50.pick_up_tip(REUSE_50_TIPS[rep]) + else: + if len(p50_INITIALTIPS) > 0: + p50.pick_up_tip(p50_INITIALTIPS[0]) + p50_INITIALTIPS.pop(0) + else: + p50_TIPS -= 1 + p50.pick_up_tip() + if tiptype == 200 and tipuse != "STP": + if p200_TIPS == 0 and len(p200_INITIALTIPS) == 0: + if tipuse == "REUSE" and tipset == "ETOH_1" and REUSE_200_TIPS_ETOH_1 == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + else: + if len(p200_RACKS_ONDECK) == 0: + protocol.comment("FIRST RACK") + else: + protocol.comment("IS THERE AN EMPTY RACK?") + RACKS_TO_DUMP.append(p200_RACKS_ONDECK[0]) + p200_RACKS_ONDECK.pop(0) + p200_RACK_COUNT += 1 + p200_TIPS += 12 + protocol.comment(f"Adding tiprack_200_{p200_RACK_COUNT}") + if len(AVAILABLE_POS_ONDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_200ul", AVAILABLE_POS_ONDECK[0], f"tiprack_200_{p200_RACK_COUNT}" + ) + AVAILABLE_POS_ONDECK.pop(0) + p200_RACKS_ONDECK.append(addtiprack) + p200_RACKS_PIPET.append(addtiprack) + p1000.tip_racks = p200_RACKS_PIPET + elif len(AVAILABLE_POS_ONDECK) == 0 and len(AVAILABLE_POS_OFFDECK) > 0: + addtiprack = protocol.load_labware( + "opentrons_flex_96_tiprack_200ul", AVAILABLE_POS_OFFDECK[0], f"tiprack_200_{p200_RACK_COUNT}" + ) + AVAILABLE_POS_OFFDECK.pop(0) + p200_RACKS_OFFDECK.append(addtiprack) + p200_RACKS_PIPET.append(addtiprack) + p1000.tip_racks = p200_RACKS_PIPET + TipRackSwap(200) + if p200_TIPS == 0 and len(p200_INITIALTIPS) > 0: + if tipuse == "REUSE" and REUSE_ANY_200_TIPS == True: + if tipset == "ETOH_1" and REUSE_200_TIPS_ETOH_1 == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + else: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + if p200_TIPS > 0: + if tipuse == "REUSE" and REUSE_ANY_200_TIPS == True: + if tipset == "ETOH_1" and REUSE_200_TIPS_ETOH_1 == True: + p1000.pick_up_tip(REUSE_200_TIPS_1[rep]) + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + else: + if len(p200_INITIALTIPS) > 0: + p1000.pick_up_tip(p200_INITIALTIPS[0]) + p200_INITIALTIPS.pop(0) + else: + p200_TIPS -= 1 + p1000.pick_up_tip() + if tiptype == 50 and tipuse == "STP": + p50.pick_up_tip(stp_50_list[rep]) + if tiptype == 200 and tipuse == "STP": + p1000.pick_up_tip(stp_200_list[rep]) + + def TipDone(tiptype, tipuse, rep, tipset): + # A Function that is called replacing the Tip dropping Step. Values are passed to the function such as tip type, reuse, and which rep (loop number) it is on. + # 1). Checks if it is a Single Tip Pickup, Reusable tip, or if the run is a Dry run, + global TIP_TRASH + if tiptype == 50: + if tipuse == "STP": + p50.drop_tip(str_50_list[rep]) if TIP_TRASH == False else p50.drop_tip() + elif tipuse == "REUSE": + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + if tiptype == 200: + if tipuse == "STP": + p1000.drop_tip(str_200_list[rep]) if TIP_TRASH == False else p1000.drop_tip() + elif tipuse == "REUSE": + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + + def TipRackSwap(tiptype): + # A Function that is called from within the TipCheck function to Swap Tip Racks. + # 1). Sets the values within the Function according to the appropriate tip rack list + # 2). If the Global Value of SWAPOFFDECK = True, it will swap tipracks (rather than dump into the Chute) + # 3). First in line of the RACKS_TO_DUMP is the one removed, can either be p50 or p200, no reusable tips or single Tip Racks + if tiptype == 50: + RACK_EMPTY = RACKS_TO_DUMP[0] + RACK_EMPTY_POS = RACKS_TO_DUMP[0].parent + RACK_NEW = p50_RACKS_OFFDECK[0] + RACK_NEW_POS = p50_RACKS_OFFDECK[0].parent + if tiptype == 200: + RACK_EMPTY = RACKS_TO_DUMP[0] + RACK_EMPTY_POS = RACKS_TO_DUMP[0].parent + RACK_NEW = p200_RACKS_OFFDECK[0] + RACK_NEW_POS = p200_RACKS_OFFDECK[0].parent + if SWAPOFFDECK == True: + SWAPSPOT.append(RACK_NEW_POS) + SWAPSPOT.append(RACK_EMPTY_POS) + protocol.comment("EMPTY POS " + str(SWAPSPOT[0])) + protocol.comment("RACK LEAVING THIS OFF DECK POS " + str(SWAPSPOT[1])) + protocol.comment("EMPTY RACK LEAVING THIS POS, MAKING IT THE NEW EMPTY POS " + str(SWAPSPOT[2])) + protocol.move_labware(labware=RACK_NEW, new_location=SWAPSPOT[0], use_gripper=USE_GRIPPER) + protocol.move_labware(labware=RACK_EMPTY, new_location=SWAPSPOT[1], use_gripper=USE_GRIPPER) + SWAPSPOT.pop(0) + SWAPSPOT.pop(0) + if tiptype == 50: + p50_RACKS_ONDECK.append(RACK_NEW) + RACKS_TO_DUMP.pop(0) + p50_RACKS_ONDECK.pop(0) + p50_RACKS_OFFDECK.pop(0) + if tiptype == 200: + p200_RACKS_ONDECK.append(RACK_NEW) + RACKS_TO_DUMP.pop(0) + p200_RACKS_ONDECK.pop(0) + p200_RACKS_OFFDECK.pop(0) + else: + SWAPS_POT = [RACK_EMPTY_POS] + protocol.move_labware(labware=RACK_EMPTY, new_location=TRASH, use_gripper=USE_GRIPPER) + protocol.move_labware(labware=RACK_NEW, new_location=SWAPS_POT[0], use_gripper=USE_GRIPPER) + if tiptype == 50: + p50_RACKS_ONDECK.append(RACK_NEW) + p50_RACKS_DROPPED.append(RACK_EMPTY) + RACKS_TO_DUMP.pop(0) + p50_RACKS_ONDECK.pop(0) + p50_RACKS_OFFDECK.pop(0) + if tiptype == 200: + p200_RACKS_ONDECK.append(RACK_NEW) + p200_RACKS_DROPPED.append(RACK_EMPTY) + RACKS_TO_DUMP.pop(0) + p200_RACKS_ONDECK.pop(0) + p200_RACKS_OFFDECK.pop(0) + + def PlateUnstack(Startposition, Stopposition): + # A Function that creates a plate, grips it based on offsets mimicking the stacked plates height, and moves it to a new position, + # This is a Standin Function until real functionality for plate unstacking is added. + global PLATE_STACKED + if PLATE_STACKED == 7: + sample_plate_1 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_1, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 6: + sample_plate_2 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_2, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 5: + sample_plate_3 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_3, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 4: + sample_plate_4 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_4, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 3: + sample_plate_5 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_5, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 2: + sample_plate_6 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_6, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": (PLATE_STACKED - 1) * 13}, + drop_offset=deck_drop_offset, + ) + PLATE_STACKED -= 1 + elif PLATE_STACKED == 1: + sample_plate_7 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", Startposition) + protocol.move_labware( + labware=sample_plate_7, + new_location=Stopposition, + use_gripper=USE_GRIPPER, + pick_up_offset={"x": 0, "y": 0, "z": 0}, + drop_offset=deck_drop_offset, + ) + + # ======================= SIMPLE SETUP ARRANGEMENT ====================== + # This is a condensed, simpler deck layout arrangement based on position. There are 2 sections, one with all the modules on deck (the NGS Workstation setup) and one without. + # This uses the DefinePosition function listed earlier, it asks for: tiptype (None, 50 or 200), position ('A1', etc.), and Status ('OPEN' for any tip, or the special uses as below) + # List all empty positions avaiable. + # DefinePosition(None,'A2','OPEN') <-- Basic, open for either tip type + # DefinePosition(None,'A2','CLOSED') <-- Tip Location is closed, used just for keeping track for the user + # DefinePosition(200,'A2','REUSE_200_1TIPS') <-- Reusable 200 tips + # DefinePosition(200,'A2','STP_200') <-- Single Tip Pickup 200 tips + # DefinePosition(200,'A2','STR_200') <-- Single Tip Return for 200 tips (testing purposes) + # Then there is a block of code for whether or not the trash is a CHUTE or BIN, note that with a BIN position A4 is not available. + + # ========== FIRST ROW =========== + if ONDECK_THERMO == True: + thermocycler = protocol.load_module("thermocycler module gen2") + sample_plate_1 = thermocycler.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Sample Plate 1") + else: + DefinePosition(None, "A1", "OPEN") + DefinePosition(None, "A2", "OPEN") + # ========== SECOND ROW ========== + if ONDECK_THERMO == True: + pass + else: + sample_plate_1 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "B1", "Sample Plate 1") + DefinePosition(None, "B2", "OPEN") + DefinePosition(50, "B3", "REUSE_50_TIPS") + # ========== THIRD ROW =========== + if ONDECK_TEMP == True: + temp_block = protocol.load_module("temperature module gen2", "C1") + temp_adapter = temp_block.load_adapter("opentrons_96_well_aluminum_block") + reagent_plate_1 = temp_adapter.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Reagent Plate") + else: + reagent_plate_1 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "C1", "Reagent Plate") + reservoir = ( + protocol.load_labware("nest_12_reservoir_15ml", "C2", "Reservoir") + if RES_TYPE_96x == False + else protocol.load_labware("nest_96_wellplate_2ml_deep", "C2") + ) + DefinePosition(None, "C3", "OPEN") + # ========== FOURTH ROW ========== + if ONDECK_HEATERSHAKER == True: + heatershaker = protocol.load_module("heaterShakerModuleV1", "D1") + CleanupPlate_1 = heatershaker.load_labware("nest_96_wellplate_2ml_deep") + else: + CleanupPlate_1 = protocol.load_labware("nest_96_wellplate_2ml_deep", "D1") + mag_block = protocol.load_module("magneticBlockV1", "D2") + # ============ TRASH ============= + if TRASH_POSITION == "BIN": + TRASH = protocol.load_trash_bin("A3") + DefinePosition(200, "D3", "REUSE_200_1TIPS") + if COLUMNS >= 5: + sample_plate_2 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "B4", "Sample Plate 2") + else: + DefinePosition(None, "B4", "OPEN") + DefinePosition(None, "C4", "OPEN") + if COLUMNS >= 5: + CleanupPlate_2 = protocol.load_labware("nest_96_wellplate_2ml_deep", "D4") + else: + DefinePosition(None, "D4", "OPEN") + if COLUMNS >= 5: + OFFDECK_LIST = ["C4", "D4"] + else: + OFFDECK_LIST = ["B4", "C4", "D4"] + if TRASH_POSITION == "CHUTE": + TRASH = protocol.load_waste_chute() + DefinePosition(200, "A3", "REUSE_200_1TIPS") + if COLUMNS >= 5: + sample_plate_2 = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A4", "Sample Plate 2") + else: + DefinePosition(None, "A4", "OPEN") + DefinePosition(None, "B4", "OPEN") + DefinePosition(None, "C4", "OPEN") + if COLUMNS >= 5: + CleanupPlate_2 = protocol.load_labware("nest_96_wellplate_2ml_deep", "D4") + else: + DefinePosition(None, "D4", "OPEN") + if COLUMNS >= 5: + OFFDECK_LIST = ["B4", "C4", "D4"] + else: + OFFDECK_LIST = ["A4", "B4", "C4", "D4"] + + # If SWAPOFFDECK = True (Meaning swapping empty On Deck Racks with New Off Deck Racks), removes the first listed tip position to keep it empty for temporary space. + if SWAPOFFDECK == True: + SWAPSPOT.append(AVAILABLE_POS_ONDECK[0]) + AVAILABLE_POS_ONDECK.pop(0) + + # Reverse the lists of Positions for accessibility (First Checked On Deck Slot is D4, Off Deck is D4) + AVAILABLE_POS_ONDECK.reverse() + AVAILABLE_POS_OFFDECK.reverse() + OFFDECK_LIST.reverse() + + # ========================== REAGENT PLATE_1 ============================ + FXENZ = reagent_plate_1["A1"] + # LIG = reagent_plate_1['A2'] + # LIG = reagent_plate_1['A3'] + Primer = reagent_plate_1["A4"] + PCR = reagent_plate_1["A5"] + # Barcodes_1 = reagent_plate_1['A7'] + # Barcodes_2 = reagent_plate_1['A8'] + # Barcodes_3 = reagent_plate_1['A9'] + # Barcodes_4 = reagent_plate_1['A10'] + # Barcodes_5 = reagent_plate_1['A11'] + # Barcodes_6 = reagent_plate_1['A12'] + + # ============================ RESERVOIR ================================ + CleanupBead = reservoir["A1"] + RSB = reservoir["A2"] + # + # EtOH = reservoir['A4'] + # EtOH = reservoir['A5'] + # EtOH = reservoir['A6'] + # + # + # + Liquid_trash_well_3 = reservoir["A10"] + Liquid_trash_well_2 = reservoir["A11"] + Liquid_trash_well_1 = reservoir["A12"] + + # ======================= TIP AND SAMPLE TRACKING ======================= + # This is a list of each column to be used in the protocol, as well as any intermediate or final sample positions. + # column_1_list = [f'A{i}' for i in range(1, COLUMNS + 1)] <-- This is a Simple list of 'A1' through 'A12', meaning a full plate. + # Example Protocols can look like this: + # if COLUMNS == 3: + # column_1_list = ['A1','A2','A3'] <-- Initial 3 columns of Samples + # column_2_list = ['A4','A5','A6'] <-- Final 3 columns of Samples + if COLUMNS == 1: + column_1_list = ["A1"] # Sample Plate 1: input and TAG + column_2_list = ["A1"] # Cleanup Plate 1: ETOH + LIG_list = ["A2"] + ETOH_1_list = ["A4"] + ETOH_2_list = ["A5"] + ETOH_3_list = ["A6"] + column_3_list = ["A5"] # Cleanup Plate 1: ETOH + column_4_list = ["A7"] # Sample Plate 1: EPM + column_5_list = ["A9"] # Cleanup Plate 1: ETOH + column_6_list = ["A9"] # Sample Plate 1: Final + barcodes = ["A7"] + if COLUMNS == 2: + column_1_list = ["A1", "A2"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2"] # Cleanup Plate 1: ETOH + LIG_list = ["A2", "A2"] + ETOH_1_list = ["A4", "A4"] + ETOH_2_list = ["A5", "A5"] + ETOH_3_list = ["A6", "A6"] + column_3_list = ["A5", "A6"] # Cleanup Plate 1: ETOH + column_4_list = ["A7", "A8"] # Sample Plate 1: EPM + column_5_list = ["A9", "A10"] # Cleanup Plate 1: ETOH + column_6_list = ["A9", "A10"] # Sample Plate 1: Final + barcodes = ["A7", "A8"] + if COLUMNS == 3: + column_1_list = ["A1", "A2", "A3"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2", "A3"] # Cleanup Plate: ETOH + LIG_list = ["A2", "A2", "A2"] + ETOH_1_list = ["A4", "A4", "A4"] + ETOH_2_list = ["A5", "A5", "A5"] + ETOH_3_list = ["A6", "A6", "A6"] + column_3_list = ["A5", "A6", "A7"] # Cleanup Plate: ETOH + column_4_list = ["A5", "A6", "A7"] # Sample Plate 1: EPM + column_5_list = ["A9", "A10", "A11"] # Cleanup Plate: ETOH + column_6_list = ["A9", "A10", "A11"] # Sample Plate 1: Final + barcodes = ["A7", "A8", "A9"] + if COLUMNS == 4: + column_1_list = ["A1", "A2", "A3", "A4"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2", "A3", "A4"] # Cleanup Plate 1: ETOH + LIG_list = ["A2", "A2", "A3", "A3"] + ETOH_1_list = ["A4", "A4", "A4", "A4"] + ETOH_2_list = ["A5", "A5", "A5", "A5"] + ETOH_3_list = ["A6", "A6", "A6", "A6"] + column_3_list = ["A5", "A6", "A7", "A8"] # Cleanup Plate 1: ETOH + column_4_list = ["A5", "A6", "A7", "A8"] # Sample Plate 1: EPM + column_5_list = ["A9", "A10", "A11", "A12"] # Cleanup Plate 1: ETOH + column_6_list = ["A9", "A10", "A11", "A12"] # Sample Plate 1: Final + barcodes = ["A7", "A8", "A9", "A10"] + if COLUMNS == 5: + column_1_list = ["A1", "A2", "A3", "A4", "A5"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2", "A3", "A4", "A5"] # Cleanup Plate 1: ETOH + LIG_list = ["A2", "A2", "A2", "A3", "A3"] + ETOH_1_list = ["A4", "A4", "A4", "A4", "A4"] + ETOH_2_list = ["A5", "A5", "A5", "A5", "A5"] + ETOH_3_list = ["A6", "A6", "A6", "A6", "A6"] + column_3_list = ["A7", "A8", "A9", "A10", "A11"] # Cleanup Plate 1: ETOH + column_4_list = ["A7", "A8", "A9", "A10", "A11"] # Sample Plate 1: EPM + column_5_list = ["A1", "A2", "A3", "A4", "A5"] # Cleanup Plate 2: ETOH + column_6_list = ["A1", "A2", "A3", "A4", "A5"] # Sample Plate 2: Final + barcodes = ["A7", "A8", "A9", "A10", "A11"] + if COLUMNS == 6: + column_1_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Sample Plate 1: input and TAG + column_2_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Cleanup Plate 1: ETOH + LIG_list = ["A2", "A2", "A2", "A3", "A3", "A3"] + ETOH_1_list = ["A4", "A4", "A4", "A4", "A4", "A4"] + ETOH_2_list = ["A5", "A5", "A5", "A5", "A5", "A5"] + ETOH_3_list = ["A6", "A6", "A6", "A6", "A6", "A6"] + column_3_list = ["A7", "A8", "A9", "A10", "A11", "A12"] # Cleanup Plate 1: ETOH + column_4_list = ["A7", "A8", "A9", "A10", "A11", "A12"] # Sample Plate 1: EPM + column_5_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Cleanup Plate 2: ETOH + column_6_list = ["A1", "A2", "A3", "A4", "A5", "A6"] # Sample Plate 2: Final + barcodes = ["A7", "A8", "A9", "A10", "A11", "A12"] + + # ============================ CUSTOM OFFSETS =========================== + # These are Custom Offsets which are a PER INSTRUMENT Setting, to account for slight adjustments of the gripper calibration or labware. + if CUSTOM_OFFSETS == True: + PCRPlate_Z_offset = 0 + Deepwell_Z_offset = 1 + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": -2, "z": 0} + hs_pick_up_offset = {"x": 0, "y": -2, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 0.0, "z": 0.5} + mb_pick_up_offset = {"x": 0, "y": -2, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": 0, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # DECK OFFSETS + deck_drop_offset = {"x": 0, "y": 0, "z": 0} + deck_pick_up_offset = {"x": 0, "y": 0, "z": 0} + else: + PCRPlate_Z_offset = 0 + Deepwell_Z_offset = 0 + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": 0, "z": 0} + hs_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 0.0, "z": 0} + mb_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": 0, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # DECK OFFSETS + deck_drop_offset = {"x": 0, "y": 0, "z": 0} + deck_pick_up_offset = {"x": 0, "y": 0, "z": 0} + + # ================================================================================================= + # ========================================= PROTOCOL START ======================================== + # ================================================================================================= + if ONDECK_THERMO == True: + thermocycler.open_lid() + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + if DRYRUN == False: + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + if ONDECK_THERMO == True: + thermocycler.set_block_temperature(4) + if ONDECK_THERMO == True: + thermocycler.set_lid_temperature(100) + if ONDECK_TEMP == True: + temp_block.set_temperature(4) + protocol.pause("Ready") + if ONDECK_HEATERSHAKER == True: + heatershaker.close_labware_latch() + Liquid_trash = Liquid_trash_well_1 + # ================================================================================================= + # ========================================= PROTOCOL START ======================================== + # ================================================================================================= + + if STEP_FXENZ == True: + protocol.comment("==============================================") + protocol.comment("--> FX") + protocol.comment("==============================================") + + protocol.comment("--> Adding FX") + FXENZBuffVol = 15 + FXENZVMixRep = 3 if DRYRUN == False else 1 + FXENZVMixVol = 20 + FXENZBuffPremix = 2 if DRYRUN == False else 1 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_1_list): + TipCheck(50, None, loop, None) + p50.mix(FXENZBuffPremix, FXENZBuffVol + 1, FXENZ.bottom(z=PCRPlate_Z_offset + 0.25)) + p50.aspirate(FXENZBuffVol + 1, FXENZ.bottom(z=PCRPlate_Z_offset + 0.25)) + p50.dispense(1, FXENZ.bottom(z=PCRPlate_Z_offset + 0.25)) + p50.dispense(FXENZBuffVol, sample_plate_1.wells_by_name()[X].bottom(z=PCRPlate_Z_offset + 0.25)) + p50.mix(FXENZVMixRep, FXENZVMixVol) + p50.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(seconds=3) + p50.blow_out(sample_plate_1[X].top(z=-3)) + TipDone(50, None, loop, None) + # =============================================== + + ############################################################################################################################################ + if ONDECK_THERMO == True: + thermocycler.close_lid() + if DRYRUN == False: + profile_FXENZ = [{"temperature": 32, "hold_time_minutes": FRAGTIME}, {"temperature": 65, "hold_time_minutes": 30}] + thermocycler.execute_profile(steps=profile_FXENZ, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(4) + thermocycler.open_lid() + else: + if DRYRUN == False: + protocol.pause("Pausing to run Fragmentation and End Repair on an off deck Thermocycler ~45min") + else: + protocol.comment("Pausing to run Fragmentation and End Repair on an off deck Thermocycler ~45min") + ############################################################################################################################################ + + if STEP_LIG == True: + protocol.comment("==============================================") + protocol.comment("--> Adapter Ligation") + protocol.comment("==============================================") + + protocol.comment("--> Adding Barcodes") + BarcodeVol = 5 + BarcodeMixRep = 3 if DRYRUN == False else 1 + BarcodeMixVol = 10 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_1_list): + TipCheck(50, None, loop, None) + p50.aspirate(BarcodeVol + 1, reagent_plate_1.wells_by_name()[barcodes[loop]].bottom(z=PCRPlate_Z_offset + 0.25)) + p50.dispense(1, reagent_plate_1.wells_by_name()[barcodes[loop]].bottom(z=PCRPlate_Z_offset + 0.25)) + p50.dispense(BarcodeVol, sample_plate_1.wells_by_name()[X].bottom(z=PCRPlate_Z_offset + 0.25)) + p50.mix(BarcodeMixRep, BarcodeMixVol) + p50.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(seconds=3) + p50.blow_out(sample_plate_1[X].top(z=-3)) + TipDone(50, None, loop, None) + # =============================================== + + protocol.comment("--> Adding Lig") + LIGVol = 45 + LIGMixRep = 10 + LIGMixVol = 80 + LIGMixPremix = 2 if DRYRUN == False else 1 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_1_list): + TipCheck(200, None, loop, None) + p1000.mix(LIGMixPremix, LIGVol + 2, reagent_plate_1.wells_by_name()[LIG_list[loop]].bottom(z=PCRPlate_Z_offset + 0.25)) + p1000.aspirate(LIGVol + 2, reagent_plate_1.wells_by_name()[LIG_list[loop]].bottom(z=PCRPlate_Z_offset + 0.25)) + p1000.dispense(2, reagent_plate_1.wells_by_name()[LIG_list[loop]].bottom(z=PCRPlate_Z_offset + 0.25)) + p1000.default_speed = 5 + p1000.move_to(reagent_plate_1.wells_by_name()[LIG_list[loop]].top(z=5)) + protocol.delay(seconds=1) + p1000.default_speed = 400 + p1000.dispense(LIGVol, sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 0.25)) + p1000.move_to(sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 0.3)) + p1000.mix(LIGMixRep, LIGMixVol, rate=0.5) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(seconds=3) + p1000.blow_out(sample_plate_1[X].top(z=-3)) + p1000.default_speed = 400 + TipDone(200, None, loop, None) + # =============================================== + + ############################################################################################################################################ + if ONDECK_THERMO == True: + thermocycler.close_lid() + if DRYRUN == False: + profile_LIG = [{"temperature": 20, "hold_time_minutes": 15}] + thermocycler.execute_profile(steps=profile_LIG, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(10) + thermocycler.open_lid() + else: + if DRYRUN == False: + protocol.pause("Pausing to run Ligation on an off deck Thermocycler ~15min") + else: + protocol.comment("Pausing to run Ligation on an off deck Thermocycler ~15min") + ############################################################################################################################################ + + if STEP_CLEANUP_1 == True: + protocol.comment("==============================================") + protocol.comment("--> Cleanup 1") + protocol.comment("==============================================") + + protocol.comment("--> ADDING CleanupBead (0.8x)") + CleanupBeadVol = 80 + SampleVol = 100 + CleanupBeadMixRPM = 1600 + CleanupBeadMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + CleanupBeadPremix = 3 if DRYRUN == False else 1 + TransferSup = 100 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_1_list): + TipCheck(200, "REUSE", loop, "ETOH_1") + p1000.mix(CleanupBeadPremix, CleanupBeadVol + 3, CleanupBead.bottom(z=Deepwell_Z_offset + 1)) + p1000.aspirate(CleanupBeadVol + 3, CleanupBead.bottom(z=Deepwell_Z_offset + 1)) + p1000.dispense(3, CleanupBead.bottom(z=Deepwell_Z_offset + 1)) + p1000.default_speed = 5 + p1000.move_to(CleanupBead.top(z=-3)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(CleanupBead.top().move(types.Point(x=4, z=-3))) + p1000.move_to(CleanupBead.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(CleanupBeadVol, CleanupPlate_1[column_2_list[loop]].bottom(z=Deepwell_Z_offset + 0.25)) + protocol.comment("--> Transferring Samples") + # ================================ + p1000.move_to(sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 0.3)) + p1000.aspirate(TransferSup / 2) + protocol.delay(seconds=0.2) + p1000.move_to(sample_plate_1[X].bottom(z=PCRPlate_Z_offset + 0.1)) + p1000.aspirate(TransferSup / 2) + p1000.dispense(TransferSup, CleanupPlate_1[column_2_list[loop]].bottom(z=Deepwell_Z_offset + 1)) + # ================================ + p1000.default_speed = 5 + p1000.move_to(CleanupPlate_1[column_2_list[loop]].bottom(z=Deepwell_Z_offset + 3)) + if TIP_MIX == True: + CleanupBeadMix = 10 + if TIP_MIX == False: + CleanupBeadMix = 2 + for Mix in range(CleanupBeadMix): + p1000.aspirate(70, rate=0.5) + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 0.25)) + p1000.aspirate(20, rate=0.5) + p1000.dispense(20, rate=0.5) + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 3)) + p1000.dispense(70, rate=0.5) + Mix += 1 + p1000.move_to(CleanupPlate_1[column_2_list[loop]].top(z=-3)) + protocol.delay(seconds=1) + p1000.blow_out(CleanupPlate_1[column_2_list[loop]].top(z=-3)) + p1000.touch_tip(speed=100) + p1000.default_speed = 400 + p1000.move_to(CleanupPlate_1[column_2_list[loop]].top(z=5)) + p1000.move_to(CleanupPlate_1[column_2_list[loop]].top(z=0)) + p1000.move_to(CleanupPlate_1[column_2_list[loop]].top(z=5)) + p1000.default_speed = 400 + TipDone(200, "REUSE", loop, "ETOH") + # =============================================== + if TIP_MIX == False: + heatershaker.set_and_wait_for_shake_speed(rpm=CleanupBeadMixRPM) + protocol.delay(CleanupBeadMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_1) HEATER SHAKER --> MAG BLOCK + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + ActualRemoveSup = 180 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # ============================= + for loop, X in enumerate(column_2_list): + TipCheck(200, "REUSE", loop, "ETOH_1") + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 2)) + p1000.aspirate(RemoveSup - 100) + protocol.delay(minutes=0.1) + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 0)) + p1000.aspirate(100) + p1000.default_speed = 200 + p1000.move_to(CleanupPlate_1[X].top(z=2)) + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(200, Liquid_trash.top(z=0)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop, "ETOH_1") + # ============================= + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + if ETOH_1_AirMultiDis == True: + TipCheck(200, None, loop, None) + for loop, X in enumerate(column_2_list): + p1000.aspirate(ETOHMaxVol, reservoir.wells_by_name()[ETOH_1_list[loop]].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + p1000.move_to(reservoir.wells_by_name()[ETOH_1_list[loop]].top(z=0)) + p1000.move_to(reservoir.wells_by_name()[ETOH_1_list[loop]].top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[ETOH_1_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[ETOH_1_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(CleanupPlate_1[X].top(z=2)) + p1000.dispense(ETOHMaxVol, rate=0.75) + protocol.delay(seconds=2) + p1000.blow_out(CleanupPlate_1[X].top(z=0)) + p1000.move_to(CleanupPlate_1[X].top(z=5)) + p1000.move_to(CleanupPlate_1[X].top(z=0)) + p1000.move_to(CleanupPlate_1[X].top(z=5)) + TipDone(200, None, loop, None) + else: + for loop, X in enumerate(column_2_list): + TipCheck(200, None, loop, None) + p1000.aspirate(ETOHMaxVol, reservoir.wells_by_name()[ETOH_1_list[loop]].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + p1000.move_to(reservoir.wells_by_name()[ETOH_1_list[loop]].top(z=0)) + p1000.move_to(reservoir.wells_by_name()[ETOH_1_list[loop]].top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[ETOH_1_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[ETOH_1_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(ETOHMaxVol, CleanupPlate_1[X].top(z=-3), rate=0.5) + protocol.delay(seconds=2) + p1000.blow_out() + p1000.move_to(CleanupPlate_1[X].top(z=5)) + p1000.move_to(CleanupPlate_1[X].top(z=0)) + p1000.move_to(CleanupPlate_1[X].top(z=5)) + TipDone(200, None, loop, None) + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + RemoveSup = 200 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_2_list): + TipCheck(200, "REUSE", loop, "ETOH_1") + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 3)) + p1000.aspirate(RemoveSup - 100) + protocol.delay(seconds=3) + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 0.75)) + p1000.aspirate(100) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate_1[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(200, Liquid_trash.top(z=-3)) + protocol.delay(seconds=2) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop, "ETOH_1") + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=1) + + for batch in range(BATCHREP): + protocol.comment("--> Removing Residual Wash") + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate( + column_2_list[batch * (int(len(column_2_list) / BATCHREP)) : (1 + batch) * int(len(column_2_list) / BATCHREP)] + ): + TipCheck(200, "REUSE", loop, "ETOH_1") + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 0.2)) + p1000.aspirate(200) + TipDone(200, "REUSE", loop, "ETOH_1") + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_1) FROM MAG BLOCK --> HEATERSHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_1, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_1, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Adding RSB") + RSBVol = 50 + RSBMixRPM = 2000 + RSBMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate( + column_2_list[batch * (int(len(column_2_list) / BATCHREP)) : (1 + batch) * int(len(column_2_list) / BATCHREP)] + ): + TipCheck(50, "REUSE", loop, "RSB") + p50.aspirate(RSBVol, RSB.bottom(z=Deepwell_Z_offset + 1)) + p50.move_to(CleanupPlate_1.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1)) + p50.dispense(RSBVol, CleanupPlate_1.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + if TIP_MIX == True: + RSBMix = 10 + if TIP_MIX == False: + RSBMix = 2 + for Mix in range(RSBMix): + p50.aspirate(RSBVol, CleanupPlate_1.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + p50.dispense(RSBVol, CleanupPlate_1.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1), rate=1) + p50.blow_out(CleanupPlate_1.wells_by_name()[X].top(z=-3)) + TipDone(50, "REUSE", loop, "RSB") + # =============================================== + if TIP_MIX == False: + heatershaker.set_and_wait_for_shake_speed(rpm=RSBMixRPM) + protocol.delay(RSBMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_1) FROM HEATERSHAKER --> MAG BLOCK + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=3) + + protocol.comment("--> Transferring Supernatant") + TransferSup = 50 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_2_list): + TipCheck(50, "REUSE", loop, "RSB") + p50.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 0.5)) + p50.aspirate(TransferSup / 2) + protocol.delay(seconds=1) + p50.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 0.2)) + p50.aspirate(TransferSup / 2) + p50.dispense(TransferSup, CleanupPlate_1[column_3_list[loop]].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + TipDone(50, "REUSE", loop, "RSB") + # =============================================== + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_1) FROM MAG BLOCK --> HEATERSHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_1, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_1, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + if STEP_CLEANUP_2 == True: + protocol.comment("==============================================") + protocol.comment("--> Cleanup 2") + protocol.comment("==============================================") + + protocol.delay(seconds=3) + + protocol.comment("--> ADDING CleanupBead (0.8x)") + CleanupBeadVol = 50 + SampleVol = 50 + CleanupBeadMixRPM = 1600 + CleanupBeadMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + CleanupBeadPremix = 3 if DRYRUN == False else 1 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_3_list): + TipCheck(200, None, loop, None) + p1000.mix(CleanupBeadPremix, CleanupBeadVol + 3, CleanupBead.bottom(z=Deepwell_Z_offset + 1)) + p1000.aspirate(CleanupBeadVol + 3, CleanupBead.bottom(z=Deepwell_Z_offset + 1)) + p1000.dispense(3, CleanupBead.bottom(z=Deepwell_Z_offset + 1)) + p1000.default_speed = 5 + p1000.move_to(CleanupBead.top(z=-3)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(CleanupBead.top().move(types.Point(x=4, z=-3))) + p1000.move_to(CleanupBead.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(CleanupBeadVol, CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 0.25)) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 3)) + if TIP_MIX == True: + CleanupBeadMix = 10 + if TIP_MIX == False: + CleanupBeadMix = 2 + for Mix in range(CleanupBeadMix): + p1000.aspirate(70, rate=0.5) + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 0.25)) + p1000.aspirate(20, rate=0.5) + p1000.dispense(20, rate=0.5) + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 3)) + p1000.dispense(70, rate=0.5) + Mix += 1 + p1000.move_to(CleanupPlate_1[X].top(z=-3)) + protocol.delay(seconds=1) + p1000.blow_out(CleanupPlate_1[X].top(z=-3)) + p1000.touch_tip(speed=100) + p1000.default_speed = 400 + p1000.move_to(CleanupPlate_1[X].top(z=5)) + p1000.move_to(CleanupPlate_1[X].top(z=0)) + p1000.move_to(CleanupPlate_1[X].top(z=5)) + TipDone(200, None, loop, None) + # =============================================== + if TIP_MIX == False: + heatershaker.set_and_wait_for_shake_speed(rpm=CleanupBeadMixRPM) + protocol.delay(CleanupBeadMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_1) HEATER SHAKER --> MAG BLOCK + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + ActualRemoveSup = 200 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop in range(4): + # for loop, X in enumerate(column_3_list): + TipCheck(200, "REUSE", loop, "ETOH_1") + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 3)) + p1000.aspirate(RemoveSup - 100) + protocol.delay(seconds=3) + p1000.move_to(CleanupPlate_1[X].bottom(z=Deepwell_Z_offset + 0.5)) + p1000.aspirate(100) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate_1[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(ActualRemoveSup, Liquid_trash.top(z=-3), rate=0.5) + protocol.delay(seconds=1) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop, "ETOH_1") + # =============================================== + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + if ETOH_1_AirMultiDis == True: + TipCheck(200, None, loop, None) + for loop, X in enumerate(column_3_list): + p1000.aspirate(ETOHMaxVol, reservoir.wells_by_name()[ETOH_2_list[loop]].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + p1000.move_to(reservoir.wells_by_name()[ETOH_2_list[loop]].top(z=0)) + p1000.move_to(reservoir.wells_by_name()[ETOH_2_list[loop]].top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[ETOH_2_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[ETOH_2_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(CleanupPlate_1[X].top(z=2)) + p1000.dispense(ETOHMaxVol, rate=0.75) + protocol.delay(seconds=2) + p1000.blow_out(CleanupPlate_1[X].top(z=0)) + p1000.move_to(CleanupPlate_1[X].top(z=5)) + p1000.move_to(CleanupPlate_1[X].top(z=0)) + p1000.move_to(CleanupPlate_1[X].top(z=5)) + TipDone(200, None, loop, None) + else: + for loop, X in enumerate(column_3_list): + TipCheck(200, None, loop, None) + p1000.aspirate(ETOHMaxVol, reservoir.wells_by_name()[ETOH_2_list[loop]].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + p1000.move_to(reservoir.wells_by_name()[ETOH_2_list[loop]].top(z=0)) + p1000.move_to(reservoir.wells_by_name()[ETOH_2_list[loop]].top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[ETOH_2_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[ETOH_2_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(ETOHMaxVol, CleanupPlate_1[X].top(z=-3), rate=0.5) + protocol.delay(seconds=2) + p1000.blow_out() + p1000.move_to(CleanupPlate_1[X].top(z=5)) + p1000.move_to(CleanupPlate_1[X].top(z=0)) + p1000.move_to(CleanupPlate_1[X].top(z=5)) + TipDone(200, None, loop, None) + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + RemoveSup = 200 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_3_list): + TipCheck(200, "REUSE", loop, "ETOH_1") + p1000.move_to(CleanupPlate_1[X].bottom(z=3)) + p1000.aspirate(RemoveSup - 100) + protocol.delay(seconds=3) + p1000.move_to(CleanupPlate_1[X].bottom(z=0.75)) + p1000.aspirate(100) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate_1[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3)) + protocol.delay(seconds=2) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop, "ETOH_1") + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=1) + + for batch in range(BATCHREP): + protocol.comment("--> Removing Residual Wash") + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate( + column_3_list[batch * (int(len(column_3_list) / BATCHREP)) : (1 + batch) * int(len(column_3_list) / BATCHREP)] + ): + TipCheck(200, "REUSE", loop, "ETOH_1") + p1000.move_to(CleanupPlate_1[X].bottom(0.2)) + p1000.aspirate(200) + TipDone(200, "REUSE", loop, "ETOH_1") + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_1) FROM MAG BLOCK --> HEATERSHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_1, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_1, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Adding RSB") + RSBVol = 22 + RSBMixRPM = 2000 + RSBMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate( + column_3_list[batch * (int(len(column_3_list) / BATCHREP)) : (1 + batch) * int(len(column_3_list) / BATCHREP)] + ): + TipCheck(50, "REUSE", loop, "RSB") + p50.aspirate(RSBVol + 2, RSB.bottom(z=1)) + p50.dispense(2, RSB.bottom(z=1)) + p50.move_to(CleanupPlate_1.wells_by_name()[X].bottom(z=1)) + p50.dispense(RSBVol, CleanupPlate_1.wells_by_name()[X].bottom(z=1), rate=0.5) + if TIP_MIX == True: + RSBMix = 10 + if TIP_MIX == False: + RSBMix = 2 + for Mix in range(RSBMix): + p50.aspirate(RSBVol, CleanupPlate_1.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + p50.dispense(RSBVol, CleanupPlate_1.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1), rate=1) + p50.blow_out(CleanupPlate_1.wells_by_name()[X].top(z=-3)) + TipDone(50, "REUSE", loop, "RSB") + # =============================================== + if TIP_MIX == False: + heatershaker.set_and_wait_for_shake_speed(rpm=RSBMixRPM) + protocol.delay(RSBMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_1) FROM HEATERSHAKER --> MAG BLOCK + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_1, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=3) + + if STEP_PCR == True: + protocol.comment("==============================================") + protocol.comment("--> Amplification") + protocol.comment("==============================================") + + protocol.comment("--> Adding Primer") + PrimerVol = 5 + PrimerMixRep = 2 + PrimerMixVol = 10 + TransferSup = 20 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_3_list): + TipCheck(50, None, loop, None) + p50.aspirate(PrimerVol, Primer.bottom(z=0.5)) + p50.dispense(PrimerVol, sample_plate_1[column_2_list[loop]].bottom(z=1)) + + protocol.comment("--> Transferring Supernatant") + p50.move_to(CleanupPlate_1[X].bottom(z=0.5)) + p50.aspirate(TransferSup / 2) + protocol.delay(seconds=1) + p50.move_to(CleanupPlate_1[X].bottom(z=0.2)) + p50.aspirate(TransferSup / 2) + p50.dispense(TransferSup, sample_plate_1[column_4_list[loop]].bottom(z=1), rate=0.5) + + p50.mix(PrimerMixRep, PrimerMixVol, rate=0.5) + TipDone(50, None, loop, None) + # =============================================== + + protocol.comment("--> Adding PCR") + PCRVol = 25 + PCRMixRep = 10 + PCRMixVol = 45 + PCRPremix = 2 if DRYRUN == False else 1 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_4_list): + TipCheck(50, None, loop, None) + p50.mix(PCRPremix, PCRVol, PCR.bottom(z=0.5)) + p50.aspirate(PCRVol, PCR.bottom(z=0.5)) + p50.dispense(PCRVol, sample_plate_1[X].bottom(z=1)) + p50.mix(PCRMixRep, PCRMixVol, rate=0.5) + p50.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(seconds=3) + p50.blow_out(sample_plate_1[X].top(z=-3)) + TipDone(50, None, loop, None) + # =============================================== + + ############################################################################################################################################ + if ONDECK_THERMO == True: + thermocycler.close_lid() + if DRYRUN == False: + profile_PCR_1 = [{"temperature": 98, "hold_time_seconds": 45}] + thermocycler.execute_profile(steps=profile_PCR_1, repetitions=1, block_max_volume=50) + profile_PCR_2 = [ + {"temperature": 98, "hold_time_seconds": 15}, + {"temperature": 60, "hold_time_seconds": 30}, + {"temperature": 72, "hold_time_seconds": 30}, + ] + thermocycler.execute_profile(steps=profile_PCR_2, repetitions=PCRCYCLES, block_max_volume=50) + profile_PCR_3 = [{"temperature": 72, "hold_time_minutes": 1}] + thermocycler.execute_profile(steps=profile_PCR_3, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(4) + thermocycler.open_lid() + else: + if DRYRUN == False: + protocol.pause("Pausing to run PCR on an off deck Thermocycler ~25min") + else: + protocol.comment("Pausing to run PCR on an off deck Thermocycler ~25min") + ############################################################################################################################################ + + if STEP_CLEANUP_3 == True: + protocol.comment("==============================================") + protocol.comment("--> Cleanup 3") + protocol.comment("==============================================") + + if COLUMNS >= 5: + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_1) FROM MAG BLOCK --> TRASH + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_1, + new_location=TRASH, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_1, + new_location=TRASH, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_2) FROM OFFDECK --> HEATERSHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_2, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_2, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + else: + + CleanupPlate_2 = CleanupPlate_1 + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_1) FROM MAG BLOCK --> HEATERSHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_1, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_1, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> ADDING CleanupBead (0.8x)") + CleanupBeadVol = 50 + SampleVol = 50 + CleanupBeadMixRPM = 1600 + CleanupBeadMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + CleanupBeadPremix = 3 if DRYRUN == False else 1 + TransferSup = 50 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_5_list): + TipCheck(200, "REUSE", loop + COLUMNS, "ETOH_1") + p1000.mix(CleanupBeadPremix, CleanupBeadVol + 3, CleanupBead.bottom(z=1)) + p1000.aspirate(CleanupBeadVol + 3, CleanupBead.bottom(z=1)) + p1000.dispense(3, CleanupBead.bottom(z=1)) + p1000.default_speed = 5 + p1000.move_to(CleanupBead.top(z=-3)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(CleanupBead.top().move(types.Point(x=4, z=-3))) + p1000.move_to(CleanupBead.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(CleanupBeadVol, CleanupPlate_2[X].bottom(z=0.25)) + protocol.comment("--> Transferring Samples") + # ================================ + p1000.move_to(sample_plate_1[column_4_list[loop]].bottom(z=0.3)) + p1000.aspirate(TransferSup / 2) + protocol.delay(seconds=0.2) + p1000.move_to(sample_plate_1[column_4_list[loop]].bottom(z=0.1)) + p1000.aspirate(TransferSup / 2) + p1000.dispense(TransferSup, CleanupPlate_2[X].bottom(z=1)) + # ================================ + p1000.default_speed = 5 + p1000.move_to(CleanupPlate_2[X].bottom(z=3)) + if TIP_MIX == True: + CleanupBeadMix = 10 + if TIP_MIX == False: + CleanupBeadMix = 2 + for Mix in range(CleanupBeadMix): + p1000.aspirate(70, rate=0.5) + p1000.move_to(CleanupPlate_2[X].bottom(z=Deepwell_Z_offset + 0.25)) + p1000.aspirate(20, rate=0.5) + p1000.dispense(20, rate=0.5) + p1000.move_to(CleanupPlate_2[X].bottom(z=Deepwell_Z_offset + 3)) + p1000.dispense(70, rate=0.5) + Mix += 1 + p1000.move_to(CleanupPlate_2[X].top(z=-3)) + protocol.delay(seconds=1) + p1000.blow_out(CleanupPlate_2[X].top(z=-3)) + p1000.touch_tip(speed=100) + p1000.default_speed = 400 + p1000.move_to(CleanupPlate_2[X].top(z=5)) + p1000.move_to(CleanupPlate_2[X].top(z=0)) + p1000.move_to(CleanupPlate_2[X].top(z=5)) + TipDone(200, "REUSE", loop + COLUMNS, "ETOH_1") + # =============================================== + if TIP_MIX == False: + heatershaker.set_and_wait_for_shake_speed(rpm=CleanupBeadMixRPM) + protocol.delay(CleanupBeadMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_2) HEATER SHAKER --> MAG BLOCK + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_2, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_2, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_5_list): + TipCheck(200, "REUSE", loop + COLUMNS, "ETOH_1") + p1000.move_to(CleanupPlate_2[X].bottom(z=3)) + p1000.aspirate(RemoveSup - 100) + protocol.delay(seconds=3) + p1000.move_to(CleanupPlate_2[X].bottom(z=0.5)) + p1000.aspirate(100) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate_2[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(200, Liquid_trash.top(z=-3), rate=0.5) + protocol.delay(seconds=1) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop + COLUMNS, "ETOH_1") + # =============================================== + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + if ETOH_1_AirMultiDis == True: + TipCheck(200, None, loop, None) + for loop, X in enumerate(column_5_list): + p1000.aspirate(ETOHMaxVol, reservoir.wells_by_name()[ETOH_3_list[loop]].bottom(z=1), rate=0.5) + p1000.move_to(reservoir.wells_by_name()[ETOH_3_list[loop]].top(z=0)) + p1000.move_to(reservoir.wells_by_name()[ETOH_3_list[loop]].top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[ETOH_3_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[ETOH_3_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(CleanupPlate_2[X].top(z=2)) + p1000.dispense(ETOHMaxVol, rate=0.75) + protocol.delay(seconds=2) + p1000.blow_out(CleanupPlate_2[X].top(z=0)) + p1000.move_to(CleanupPlate_2[X].top(z=5)) + p1000.move_to(CleanupPlate_2[X].top(z=0)) + p1000.move_to(CleanupPlate_2[X].top(z=5)) + TipDone(200, None, loop, None) + else: + for loop, X in enumerate(column_5_list): + TipCheck(200, None, loop, None) + p1000.aspirate(ETOHMaxVol, reservoir.wells_by_name()[ETOH_3_list[loop]].bottom(z=1), rate=0.5) + p1000.move_to(reservoir.wells_by_name()[ETOH_3_list[loop]].top(z=0)) + p1000.move_to(reservoir.wells_by_name()[ETOH_3_list[loop]].top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(reservoir.wells_by_name()[ETOH_3_list[loop]].top().move(types.Point(x=4, z=-3))) + p1000.move_to(reservoir.wells_by_name()[ETOH_3_list[loop]].top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(ETOHMaxVol, CleanupPlate_2[X].top(z=-3), rate=0.5) + protocol.delay(seconds=2) + p1000.blow_out() + p1000.move_to(CleanupPlate_2[X].top(z=5)) + p1000.move_to(CleanupPlate_2[X].top(z=0)) + p1000.move_to(CleanupPlate_2[X].top(z=5)) + TipDone(200, None, loop, None) + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + RemoveSup = 200 + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_5_list): + TipCheck(200, "REUSE", loop + COLUMNS, "ETOH_1") + p1000.move_to(CleanupPlate_2[X].bottom(z=3)) + p1000.aspirate(RemoveSup - 100) + protocol.delay(seconds=3) + p1000.move_to(CleanupPlate_2[X].bottom(z=0.75)) + p1000.aspirate(100) + p1000.default_speed = 5 + p1000.move_to(CleanupPlate_2[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + # ======L Waste Volume Check====== + WASTEVOL += ActualRemoveSup * 8 + protocol.comment("Adding " + str((ActualRemoveSup * 8)) + "ul tp " + str(WASTEVOL)) + if WASTEVOL < 14400: + Liquid_trash = Liquid_trash_well_1 + if WASTEVOL >= 14400 and WASTEVOL < 28800: + Liquid_trash = Liquid_trash_well_2 + if WASTEVOL >= 28800: + Liquid_trash = Liquid_trash_well_3 + # ================================ + p1000.dispense(200, Liquid_trash.top(z=-3)) + protocol.delay(seconds=2) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + TipDone(200, "REUSE", loop + COLUMNS, "ETOH_1") + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=1) + + for batch in range(BATCHREP): + protocol.comment("--> Removing Residual Wash") + p1000.flow_rate.aspirate = p1000_flow_rate_aspirate_default * 0.5 + p1000.flow_rate.dispense = p1000_flow_rate_dispense_default * 0.5 + p1000.flow_rate.blow_out = p1000_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate( + column_5_list[batch * (int(len(column_5_list) / BATCHREP)) : (1 + batch) * int(len(column_5_list) / BATCHREP)] + ): + TipCheck(200, "REUSE", loop + COLUMNS, "ETOH_1") + p1000.move_to(CleanupPlate_2[X].bottom(0.2)) + p1000.aspirate(50) + TipDone(200, "REUSE", loop + COLUMNS, "ETOH_1") + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_2) FROM MAG BLOCK --> HEATERSHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_2, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_2, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Adding RSB") + RSBVol = 26 + RSBMixRPM = 2000 + RSBMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate( + column_5_list[batch * (int(len(column_5_list) / BATCHREP)) : (1 + batch) * int(len(column_5_list) / BATCHREP)] + ): + TipCheck(50, "REUSE", loop + COLUMNS, "RSB") + p50.aspirate(RSBVol + 2, RSB.bottom(z=1)) + p50.dispense(2, RSB.bottom(z=1)) + p50.move_to(CleanupPlate_2.wells_by_name()[X].bottom(z=1)) + p50.dispense(RSBVol, CleanupPlate_2.wells_by_name()[X].bottom(z=1), rate=0.5) + if TIP_MIX == True: + CleanupBeadMix = 10 + if TIP_MIX == False: + CleanupBeadMix = 2 + for Mix in range(RSBMix): + p50.aspirate(RSBVol, CleanupPlate_2.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1), rate=0.5) + p50.dispense(RSBVol, CleanupPlate_2.wells_by_name()[X].bottom(z=Deepwell_Z_offset + 1), rate=1) + p50.blow_out(CleanupPlate_2.wells_by_name()[X].top(z=-3)) + TipDone(50, "REUSE", loop + COLUMNS, "RSB") + # =============================================== + if TIP_MIX == False: + heatershaker.set_and_wait_for_shake_speed(rpm=RSBMixRPM) + protocol.delay(RSBMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (CleanupPlate_2) FROM HEATERSHAKER --> MAG BLOCK + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=CleanupPlate_2, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=CleanupPlate_2, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=mb_drop_offset, + ) + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=3) + + if COLUMNS >= 5: + # ============================================================================================ + # GRIPPER MOVE (sample_plate_2) FROM OFF DECK --> HEATERSHAKER + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + protocol.move_labware( + labware=sample_plate_2, + new_location=heatershaker, + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + else: + protocol.move_labware( + labware=sample_plate_2, + new_location="D1", + use_gripper=USE_GRIPPER, + pick_up_offset=deck_pick_up_offset, + drop_offset=deck_drop_offset, + ) + # ============================================================================================ + + protocol.comment("--> Transferring Supernatant") + TransferSup = 25 + p50.flow_rate.aspirate = p50_flow_rate_aspirate_default * 0.5 + p50.flow_rate.dispense = p50_flow_rate_dispense_default * 0.5 + p50.flow_rate.blow_out = p50_flow_rate_blow_out_default * 0.5 + # =============================================== + for loop, X in enumerate(column_5_list): + TipCheck(50, "REUSE", loop + COLUMNS, "RSB") + p50.move_to(CleanupPlate_2[X].bottom(z=0.5)) + p50.aspirate(TransferSup / 2) + protocol.delay(seconds=1) + p50.move_to(CleanupPlate_2[X].bottom(z=0.2)) + p50.aspirate(TransferSup / 2) + if COLUMNS >= 5: + p50.dispense(TransferSup, sample_plate_2[column_6_list[loop]].bottom(z=1), rate=0.5) + else: + p50.dispense(TransferSup, sample_plate_1[column_6_list[loop]].bottom(z=1), rate=0.5) + TipDone(50, "REUSE", loop + COLUMNS, "RSB") + # =============================================== + + # ================================================================================================= + # ========================================== PROTOCOL END ========================================= + # ================================================================================================= + if DEACTIVATE_TEMP == True: + if ONDECK_THERMO == True: + thermocycler.deactivate_block() + if ONDECK_THERMO == True: + thermocycler.deactivate_lid() + if ONDECK_TEMP == True: + temp_block.deactivate() + if ONDECK_HEATERSHAKER == True: + heatershaker.open_labware_latch() + # ================================================================================================= + # ========================================== PROTOCOL END ========================================= + # ================================================================================================= + + protocol.comment("==============================================") + protocol.comment("--> Report") + protocol.comment("==============================================") + # This is a section that will print out the various lists to help keep track of modifying the protocol, set the REPORT step to False to ignore. + if REPORT == True: + protocol.comment("REUSE_50_TIPS " + str(REUSE_50_TIPS)) + protocol.comment("p50_INITIALTIPS " + str(p50_INITIALTIPS)) + protocol.comment("REUSE_200_TIPS_1 " + str(REUSE_200_TIPS_1)) + protocol.comment("REUSE_200_TIPS_2 " + str(REUSE_200_TIPS_2)) + protocol.comment("p200_INITIALTIPS " + str(p200_INITIALTIPS)) + protocol.comment("SWAPSPOT " + str(SWAPSPOT)) + protocol.comment("AVAILABLE_POS_ONDECK " + str(AVAILABLE_POS_ONDECK)) + protocol.comment("AVAILABLE_POS_OFFDECK " + str(AVAILABLE_POS_OFFDECK)) + protocol.comment("REUSE_50_TIPS_COUNT " + str(REUSE_50_TIPS_COUNT)) + protocol.comment("REUSE_200_TIPS_COUNT " + str(REUSE_200_TIPS_COUNT)) + protocol.comment("p50_RACKS_ONDECK " + str(p50_RACKS_ONDECK)) + protocol.comment("p50_RACKS_OFFDECK " + str(p50_RACKS_OFFDECK)) + protocol.comment("p50_RACKS_DROPPED " + str(p50_RACKS_DROPPED)) + protocol.comment("p50_TIPS " + str(p50_TIPS)) + protocol.comment("p50_RACKS_PIPET " + str(p50_RACKS_PIPET)) + protocol.comment("p200_RACKS_ONDECK " + str(p200_RACKS_ONDECK)) + protocol.comment("p200_RACKS_OFFDECK " + str(p200_RACKS_OFFDECK)) + protocol.comment("p200_RACKS_DROPPED " + str(p200_RACKS_DROPPED)) + protocol.comment("p200_TIPS " + str(p200_TIPS)) + protocol.comment("p200_RACKS_PIPET " + str(p200_RACKS_PIPET)) + protocol.comment("RACKS_TO_DUMP " + str(RACKS_TO_DUMP)) + + # This is a section that is used to define liquids, and label wells, this is optional, and unconnected from the rest of the protocol, used only for the App and Website + # This is at the end because it adds lines of code to the runtime that can be at the end rather than the beginning, since it has no effect on the protocol setps. + + if NOLABEL == True: + # PROTOCOL SETUP - LABELING + + # ======== ESTIMATING LIQUIDS ======= + Sample_Volume = 40 + CleanupBead_Volume = COLUMNS * (180) + ETOH_Volume = COLUMNS * (900) + RSB_Volume = COLUMNS * (98) + FXENZ_Volume = COLUMNS * (15) + LIG_Volume = COLUMNS * (45) + Primer_Volume = COLUMNS * (5) + PCR_Volume = COLUMNS * (25) + + TotalColumn = ["A", "B", "C", "D", "E", "F", "G", "H"] + + # ======== DEFINING LIQUIDS ======= + CleanupBead = protocol.define_liquid( + name="EtOH", description="CleanupBead Beads", display_color="#704848" + ) # 704848 = 'CleanupBead Brown' + EtOH = protocol.define_liquid(name="EtOH", description="80% Ethanol", display_color="#9ACECB") # 9ACECB = 'Ethanol Blue' + RSB = protocol.define_liquid(name="RSB", description="Resuspension Buffer", display_color="#00FFF2") # 00FFF2 = 'Base Light Blue' + Liquid_trash_well = protocol.define_liquid( + name="Liquid_trash_well", description="Liquid Trash", display_color="#9B9B9B" + ) # 9B9B9B = 'Liquid Trash Grey' + Sample = protocol.define_liquid(name="Sample", description="Sample", display_color="#52AAFF") # 52AAFF = 'Sample Blue' + FXENZ = protocol.define_liquid(name="FXENZ", description="FX Enzyme", display_color="#FF0000") # FF0000 = 'Base Red' + LIG = protocol.define_liquid(name="LIG", description="Ligation Mix", display_color="#FFA000") # FFA000 = 'Base Orange' + Primer = protocol.define_liquid(name="Primer", description="Primer", display_color="#FFFB00") # FFFB00 = 'Base Yellow' + PCR = protocol.define_liquid(name="PCR", description="PCR Mix", display_color="#0EFF00") # 0EFF00 = 'Base Green' + Barcodes = protocol.define_liquid(name="Barcodes", description="Barcodes", display_color="#7DFFC4") # 7DFFC4 = 'Barcode Green' + Final_Sample = protocol.define_liquid( + name="Final_Sample", description="Final Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + Placeholder_Sample = protocol.define_liquid( + name="Placeholder_Sample", description="Placeholder Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + + # ======== LOADING LIQUIDS ======= + if RES_TYPE_96x == "12x15ml": + reservoir.wells_by_name()["A1"].load_liquid(liquid=CleanupBead, volume=CleanupBead_Volume) + reservoir.wells_by_name()["A2"].load_liquid(liquid=RSB, volume=RSB_Volume) + reservoir.wells_by_name()["A4"].load_liquid(liquid=EtOH, volume=(ETOH_Volume / 3)) + reservoir.wells_by_name()["A5"].load_liquid(liquid=EtOH, volume=(ETOH_Volume / 3)) + reservoir.wells_by_name()["A6"].load_liquid(liquid=EtOH, volume=(ETOH_Volume / 3)) + reservoir.wells_by_name()["A10"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()["A11"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()["A12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if RES_TYPE_96x == "96x2ml": + for loop, X in enumerate(TotalColumn): + reservoir.wells_by_name()[X + "1"].load_liquid(liquid=CleanupBead, volume=CleanupBead_Volume) + reservoir.wells_by_name()[X + "2"].load_liquid(liquid=RSB, volume=RSB_Volume) + reservoir.wells_by_name()[X + "4"].load_liquid(liquid=EtOH, volume=(ETOH_Volume / 3)) + reservoir.wells_by_name()[X + "5"].load_liquid(liquid=EtOH, volume=(ETOH_Volume / 3)) + reservoir.wells_by_name()[X + "6"].load_liquid(liquid=EtOH, volume=(ETOH_Volume / 3)) + reservoir.wells_by_name()[X + "10"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()[X + "11"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()[X + "12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if COLUMNS >= 1: + if COLUMNS < 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "1"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 2: + if COLUMNS < 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "6"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "2"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 3: + if COLUMNS < 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "3"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "11"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "3"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "3"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 4: + if COLUMNS < 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "4"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "12"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "4"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "4"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 5: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "11"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "5"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "11"].load_liquid(liquid=Barcodes, volume=5) + if COLUMNS >= 6: + for loop, X in enumerate(TotalColumn): + sample_plate_1.wells_by_name()[X + "6"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "12"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_2.wells_by_name()[X + "6"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "12"].load_liquid(liquid=Barcodes, volume=5) + for loop, X in enumerate(TotalColumn): + reagent_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=FXENZ, volume=FXENZ_Volume) + if COLUMNS < 6: + reagent_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=LIG, volume=LIG_Volume) + if COLUMNS == 6: + reagent_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=LIG, volume=(LIG_Volume / 2)) + reagent_plate_1.wells_by_name()[X + "3"].load_liquid(liquid=LIG, volume=(LIG_Volume / 2)) + reagent_plate_1.wells_by_name()[X + "4"].load_liquid(liquid=PCR, volume=PCR_Volume) diff --git a/app-testing/files/protocols/pl_QIAseq_FX_24x_Normalizer_Workflow_B.py b/app-testing/files/protocols/pl_QIAseq_FX_24x_Normalizer_Workflow_B.py new file mode 100644 index 00000000000..815ffd1592f --- /dev/null +++ b/app-testing/files/protocols/pl_QIAseq_FX_24x_Normalizer_Workflow_B.py @@ -0,0 +1,1956 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"DRYRUN":false,"USE_GRIPPER":true,"TIP_TRASH":true,"DEACTIVATE_TEMP":true,"COLUMNS":3,"FRAGTIME":15,"PCRCYCLES":6,"DEFAULT_OFFSETS":true,"protocol_filename":"QIAseq_FX_24x_Normalizer_Workflow_B"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons import protocol_api +from opentrons import types + +metadata = { + "protocolName": "QIAseq FX 24x + Normalizer Workflow B v5", + "author": "Opentrons ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + +# SCRIPT SETTINGS +DRYRUN = False # True = Skip incubation times, shorten mix, No Temperatures, for testing purposes +USE_GRIPPER = True # True = Uses Gripper, False = Manual Move +TIP_TRASH = True # True = Used tips go in Trash, False = Used tips go back into rack +DEACTIVATE_TEMP = True # True = Deactivates Temp Block and Thermocycler, False = Leaves Temp Block and Thermocycler on (if leaving finished plates on deck) +DEFAULT_OFFSETS = True # True = Uses Default Module Gripper Offsets, False = Use user-defined adjusted Offsets + +# PROTOCOL SETTINGS +COLUMNS = 3 # 1-3 Columns +FRAGTIME = 15 # Minutes, Duration of the Fragmentation Step +PCRCYCLES = 6 # Amount of PCR Cycles +RES_TYPE = "12x15ml" # '12x15ml' or '96x2ml' +DEFAULT_OFFSETS = False # True = Uses Default Module Gripper Offsets, False = Use user-defined adjusted Offsets +ETOH_1_AirMultiDis = False +RSB_1_AirMultiDis = False +ETOH_2_AirMultiDis = False +RSB_2_AirMultiDis = False +ETOH_3_AirMultiDis = False +RSB_3_AirMultiDis = False +NWASH_AirMultiDis = False +REUSE_TIPS = False + +# PROTOCOL BLOCKS +STEP_FXENZ = 1 # Set to 0 to skip block of commands +STEP_FXDECK = 1 # Set to 0 if using off deck thermocycler +STEP_LIG = 1 # Set to 0 to skip block of commands +STEP_LIGDECK = 1 # Set to 0 if using off deck thermocycler +STEP_CLEANUP_1 = 1 # Set to 0 to skip block of commands +STEP_CLEANUP_2 = 1 # Set to 0 to skip block of commands +STEP_PCR = 1 # Set to 0 to skip block of commands +STEP_PCRDECK = 1 # Set to 0 if using off deck thermocycler +STEP_CLEANUP_3 = 1 # Set to 0 to skip block of commands +STEP_NORMALIZE = 1 # Set to 0 to skip block of commands + +# Notes +# The PCR Primer is diluted to from 1.5ul to 5ul with RSB, and the Samples are eluted in 20ul RSB instead of 23.5 after Cleanup 2. This because the Flex can handle volumes above 1.5ul more reliably +# The Input Samples contain 100ng of DNA in 35ul H20 +# 5ul of FX Buffer is mixed with the DNA Input Samples +# +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ + +p200_tips = 0 +p50_tips = 0 +p200_tipracks_count = 0 +p50_tipracks_count = 0 +WasteVol = 0 +Resetcount = 0 + +ABR_TEST = False +if ABR_TEST == True: + COLUMNS = 3 # Overrides to 3 columns + DRYRUN = True # Overrides to only DRYRUN + TIP_TRASH = False # Overrides to only REUSING TIPS + RUN = 3 # Repetitions +else: + RUN = 1 + + +def run(protocol: protocol_api.ProtocolContext): + + global DRYRUN + global USE_GRIPPER + global TIP_TRASH + global DEACTIVATE_TEMP + global COLUMNS + global FRAGTIME + global PCRCYCLES + global DEFAULT_OFFSETS + + global p200_tips + global p50_tips + global p200_tipracks_count + global p50_tipracks_count + global WasteVol + global Resetcount + + if ABR_TEST == True: + protocol.comment("THIS IS A ABR RUN WITH " + str(RUN) + " REPEATS") + protocol.comment("THIS IS A DRY RUN") if DRYRUN == True else protocol.comment("THIS IS A REACTION RUN") + protocol.comment("USED TIPS WILL GO IN TRASH") if TIP_TRASH == True else protocol.comment("USED TIPS WILL BE RE-RACKED") + + # DECK SETUP AND LABWARE + # ========== FIRST ROW ============ + heatershaker = protocol.load_module("heaterShakerModuleV1", "D1") + hs_adapter = heatershaker.load_adapter("opentrons_96_deep_well_adapter") + BeadPlate = hs_adapter.load_labware("nest_96_wellplate_2ml_deep", "Bead Plate") + mag_block = protocol.load_module("magneticBlockV1", "D2") + tiprack_50_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "D3") + # ========== SECOND ROW =========== + temp_block = protocol.load_module("temperature module gen2", "C1") + temp_adapter = temp_block.load_adapter("opentrons_96_well_aluminum_block") + reagent_plate_1 = temp_adapter.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "Reagent Plate") + if RES_TYPE == "12x15ml": + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "C2", "Reservoir") + if RES_TYPE == "96x2ml": + reservoir = protocol.load_labware("nest_96_wellplate_2ml_deep", "C2", "Reservoir") + tiprack_50_2 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "C3") + # ========== THIRD ROW ============ + thermocycler = protocol.load_module("thermocycler module gen2") + sample_plate_1 = thermocycler.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "Sample Plate") + tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "B2") + tiprack_200_2 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "B3") + # ========== FOURTH ROW =========== + tiprack_200_3 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "A2") + protocol.load_trash_bin("A3") + + ############################################################################################################################################ + ############################################################################################################################################ + ############################################################################################################################################ + + # PROTOCOL SETUP - LABELING + + # ======== ESTIMATING LIQUIDS ======= + Sample_Volume = 40 + AMPure_Volume = COLUMNS * (80 + 50 + 50) * 1.1 + ETOH_Volume = COLUMNS * ((150 * 2) * 3) * 1.1 + RSB_Volume = COLUMNS * (50 + 20 + 25) * 1.1 + FXENZ_Volume = COLUMNS * (10) * 1.1 + LIG_Volume = COLUMNS * (45) * 1.1 + Primer_Volume = COLUMNS * (5) * 1.1 + PCR_Volume = COLUMNS * (25) * 1.1 + NormBead_Volume = COLUMNS * (25) * 1.1 + NormElute_Volume = COLUMNS * (25) * 1.1 + NormWash_Volume = COLUMNS * (25) * 1.1 + + SampleColumn = ["A", "B", "C", "D", "E", "F", "G", "H"] + + # ======== DEFINING LIQUIDS ======= + AMPure = protocol.define_liquid(name="EtOH", description="AMPure Beads", display_color="#704848") # 704848 = 'AMPure Brown' + EtOH = protocol.define_liquid(name="EtOH", description="80% Ethanol", display_color="#9ACECB") # 9ACECB = 'Ethanol Blue' + RSB = protocol.define_liquid(name="RSB", description="Resuspension Buffer", display_color="#00FFF2") # 00FFF2 = 'Base Light Blue' + Liquid_trash_well = protocol.define_liquid( + name="Liquid_trash_well", description="Liquid Trash", display_color="#9B9B9B" + ) # 9B9B9B = 'Liquid Trash Grey' + Sample = protocol.define_liquid(name="Sample", description="Sample", display_color="#52AAFF") # 52AAFF = 'Sample Blue' + FXENZ = protocol.define_liquid(name="FXENZ", description="FX Enzyme", display_color="#FF0000") # FF0000 = 'Base Red' + LIG = protocol.define_liquid(name="LIG", description="Ligation Mix", display_color="#FFA000") # FFA000 = 'Base Orange' + Primer = protocol.define_liquid(name="Primer", description="Primer", display_color="#FFFB00") # FFFB00 = 'Base Yellow' + PCR = protocol.define_liquid(name="PCR", description="PCR Mix", display_color="#0EFF00") # 0EFF00 = 'Base Green' + Barcodes = protocol.define_liquid(name="Barcodes", description="Barcodes", display_color="#7DFFC4") # 7DFFC4 = 'Barcode Green' + NormBead = protocol.define_liquid(name="NormBead", description="NormBead", display_color="#0082FF") # 0082FF = 'Base Blue' + NormElute = protocol.define_liquid(name="NormElute", description="NormElute", display_color="#0004FF") # 0004FF = 'Base Indigo' + NormWash = protocol.define_liquid(name="NormWash", description="NormWash", display_color="#8400FF") # 8400FF = 'Base Purple' + Final_Sample = protocol.define_liquid( + name="Final_Sample", description="Final Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + Excess_Sample = protocol.define_liquid( + name="Excess_Sample", description="Excess Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + Placeholder_Sample = protocol.define_liquid( + name="Placeholder_Sample", description="Placeholder Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + Placeholder_NormWash = protocol.define_liquid( + name="Placeholder_NormWash", description="Excess Sample", display_color="#82A9CF" + ) # 82A9CF = 'Placeholder Blue' + + # ======== LOADING LIQUIDS ======= + if RES_TYPE == "12x15ml": + reservoir.wells_by_name()["A1"].load_liquid(liquid=AMPure, volume=AMPure_Volume) + reservoir.wells_by_name()["A3"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()["A4"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()["A5"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()["A7"].load_liquid(liquid=RSB, volume=RSB_Volume) + reservoir.wells_by_name()["A8"].load_liquid(liquid=NormWash, volume=NormWash_Volume) + reservoir.wells_by_name()["A9"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()["A10"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()["A11"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()["A12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if RES_TYPE == "96x2ml": + for loop, X in enumerate(SampleRow): + reservoir.wells_by_name()[X + "1"].load_liquid(liquid=AMPure, volume=AMPure_Volume) + reservoir.wells_by_name()[X + "3"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()[X + "4"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()[X + "5"].load_liquid(liquid=EtOH, volume=ETOH_Volume) + reservoir.wells_by_name()[X + "7"].load_liquid(liquid=RSB, volume=RSB_Volume) + reservoir.wells_by_name()[X + "8"].load_liquid(liquid=NormWash, volume=NormWash_Volume) + reservoir.wells_by_name()[X + "9"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()[X + "10"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()[X + "11"].load_liquid(liquid=Liquid_trash_well, volume=0) + reservoir.wells_by_name()[X + "12"].load_liquid(liquid=Liquid_trash_well, volume=0) + if COLUMNS >= 1: + for loop, X in enumerate(SampleColumn): + sample_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "4"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Placeholder_NormWash, volume=0) + sample_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "7"].load_liquid(liquid=Barcodes, volume=5) + reagent_plate_1.wells_by_name()[X + "10"].load_liquid(liquid=Excess_Sample, volume=0) + if COLUMNS >= 2: + for loop, X in enumerate(SampleColumn): + sample_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Placeholder_NormWash, volume=0) + sample_plate_1.wells_by_name()[X + "11"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "8"].load_liquid(liquid=Barcodes, volume=5) + reagent_plate_1.wells_by_name()[X + "11"].load_liquid(liquid=Excess_Sample, volume=0) + if COLUMNS >= 3: + for loop, X in enumerate(SampleColumn): + sample_plate_1.wells_by_name()[X + "3"].load_liquid(liquid=Sample, volume=Sample_Volume) + sample_plate_1.wells_by_name()[X + "6"].load_liquid(liquid=Placeholder_Sample, volume=0) + sample_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Placeholder_NormWash, volume=0) + sample_plate_1.wells_by_name()[X + "12"].load_liquid(liquid=Final_Sample, volume=0) + reagent_plate_1.wells_by_name()[X + "9"].load_liquid(liquid=Barcodes, volume=5) + reagent_plate_1.wells_by_name()[X + "12"].load_liquid(liquid=Excess_Sample, volume=0) + for loop, X in enumerate(SampleColumn): + reagent_plate_1.wells_by_name()[X + "1"].load_liquid(liquid=FXENZ, volume=FXENZ_Volume) + reagent_plate_1.wells_by_name()[X + "2"].load_liquid(liquid=LIG, volume=LIG_Volume) + reagent_plate_1.wells_by_name()[X + "3"].load_liquid(liquid=Primer, volume=Primer_Volume) + reagent_plate_1.wells_by_name()[X + "4"].load_liquid(liquid=PCR, volume=PCR_Volume) + reagent_plate_1.wells_by_name()[X + "5"].load_liquid(liquid=NormBead, volume=NormBead_Volume) + reagent_plate_1.wells_by_name()[X + "6"].load_liquid(liquid=NormElute, volume=NormElute_Volume) + + ############################################################################################################################################ + ############################################################################################################################################ + ############################################################################################################################################ + + # PROTOCOL SETUP - SCRIPT DEFINITIONS + + # =========== RESERVOIR =========== + AMPure = reservoir["A1"] + EtOH_1 = reservoir["A3"] + EtOH_2 = reservoir["A4"] + EtOH_3 = reservoir["A5"] + RSB = reservoir["A7"] + NormWash = reservoir["A8"] + Liquid_trash_well_4 = reservoir["A9"] + Liquid_trash_well_3 = reservoir["A10"] + Liquid_trash_well_2 = reservoir["A11"] + Liquid_trash_well_1 = reservoir["A12"] + + # ========= REAGENT PLATE ========== + FXENZ = reagent_plate_1.wells_by_name()["A1"] + LIG = reagent_plate_1.wells_by_name()["A2"] + Primer = reagent_plate_1.wells_by_name()["A3"] + PCR = reagent_plate_1.wells_by_name()["A4"] + NormBead = reagent_plate_1.wells_by_name()["A5"] + NormElute = reagent_plate_1.wells_by_name()["A6"] + Barcodes_1 = reagent_plate_1.wells_by_name()["A7"] + Barcodes_2 = reagent_plate_1.wells_by_name()["A8"] + Barcodes_3 = reagent_plate_1.wells_by_name()["A9"] + + # PIPETTE + p1000 = protocol.load_instrument("flex_8channel_1000", "left", tip_racks=[tiprack_200_1, tiprack_200_2, tiprack_200_3]) + p50 = protocol.load_instrument("flex_8channel_50", "right", tip_racks=[tiprack_50_1, tiprack_50_2]) + if REUSE_TIPS == True: + ETOH_AirMultiDis_Tip = tiprack_200_1.wells_by_name()["A1"] + ETOH_RemoveSup_Tip_1 = tiprack_200_1.wells_by_name()["A2"] + ETOH_RemoveSup_Tip_2 = tiprack_200_1.wells_by_name()["A3"] + ETOH_RemoveSup_Tip_3 = tiprack_200_1.wells_by_name()["A4"] + ETOH_RemoveSup_Tip = [ETOH_RemoveSup_Tip_1, ETOH_RemoveSup_Tip_2, ETOH_RemoveSup_Tip_3] + NWASH_AirMultiDis_Tip = tiprack_200_1.wells_by_name()["A5"] + NWASH_Tip_1 = tiprack_200_1.wells_by_name()["A6"] + NWASH_Tip_2 = tiprack_200_1.wells_by_name()["A7"] + NWASH_Tip_3 = tiprack_200_1.wells_by_name()["A8"] + NWASH_Tip = [NWASH_Tip_1, NWASH_Tip_2, NWASH_Tip_3] + p1000.starting_tip = tiprack_200_1.wells_by_name()["A9"] + p200_tips += 8 + RSB_AirMultiDis_Tip = tiprack_50_1.wells_by_name()["A1"] + NELUTE_Tip_1 = tiprack_200_1.wells_by_name()["A2"] + NELUTE_Tip_2 = tiprack_200_1.wells_by_name()["A3"] + NELUTE_Tip_3 = tiprack_200_1.wells_by_name()["A4"] + NELUTE_Tip = [NELUTE_Tip_1, NELUTE_Tip_2, NELUTE_Tip_3] + p50.starting_tip = tiprack_50_1.wells_by_name()["A5"] + p50_tips += 4 + p1000_flow_rate_aspirate_default = 200 + p1000_flow_rate_dispense_default = 200 + p1000_flow_rate_blow_out_default = 400 + p50_flow_rate_aspirate_default = 50 + p50_flow_rate_dispense_default = 50 + p50_flow_rate_blow_out_default = 100 + p200_tipracks = 3 + p50_tipracks = 2 + + # SAMPLE TRACKING + if COLUMNS == 1: + column_1_list = ["A1"] # Sample_plate_1 - Initial Samples + column_2_list = ["A4"] # Sample_plate_1 - PCR + column_6_list = ["A7"] # Sample_plate_1 - Normwash + column_9_list = ["A10"] # sample_plate_1 - Final Samples + column_A_list = ["A1"] # BeadPlate - Cleanup #1 + column_B_list = ["A4"] # BeadPlate - Cleanup #2 + column_C_list = ["A7"] # BeadPlate - Cleanup #3 + column_8_list = ["A10"] # BeadPlate - Normwash + column_7_list = ["A10"] # Reagent Plate - Pre-Normed Samples + barcodes = ["A7"] # Reagent Plate + if COLUMNS == 2: + column_1_list = ["A1", "A2"] # Sample_plate_1 - Initial Samples + column_2_list = ["A4", "A5"] # Sample_plate_1 - PCR + column_6_list = ["A7", "A8"] # Sample_plate_1 - Normwash + column_9_list = ["A10", "A11"] # sample_plate_1 - Final Samples + column_A_list = ["A1", "A2"] # BeadPlate - Cleanup #1 + column_B_list = ["A4", "A5"] # BeadPlate - Cleanup #2 + column_C_list = ["A7", "A8"] # BeadPlate - Cleanup #3 + column_8_list = ["A10", "A11"] # BeadPlate - Normwash + column_7_list = ["A10", "A11"] # Reagent Plate - Pre-Normed Samples + barcodes = ["A7", "A8"] # Reagent Plate + if COLUMNS == 3: + column_1_list = ["A1", "A2", "A3"] # Sample_plate_1 - Initial Samples + column_2_list = ["A4", "A5", "A6"] # Sample_plate_1 - PCR + column_6_list = ["A7", "A8", "A9"] # Sample_plate_1 - Normwash + column_9_list = ["A10", "A11", "A12"] # sample_plate_1 - Final Samples + column_A_list = ["A1", "A2", "A3"] # BeadPlate - Cleanup #1 + column_B_list = ["A4", "A5", "A6"] # BeadPlate - Cleanup #2 + column_C_list = ["A7", "A8", "A9"] # BeadPlate - Cleanup #3 + column_8_list = ["A10", "A11", "A12"] # BeadPlate - Normwash + column_7_list = ["A10", "A11", "A12"] # Reagent Plate - Pre-Normed Samples + barcodes = ["A7", "A8", "A9"] # Reagent Plate + + def tipcheck(tiptype): + global p200_tips + global p50_tips + global p200_tipracks_count + global p50_tipracks_count + global Resetcount + if tiptype == 200: + if p200_tips == p200_tipracks * 12: + if ABR_TEST == True: + p1000.reset_tipracks() + else: + protocol.pause("RESET p200 TIPS") + p1000.reset_tipracks() + Resetcount += 1 + p200_tipracks_count += 1 + p200_tips = 0 + if tiptype == 50: + if p50_tips == p50_tipracks * 12: + if ABR_TEST == True: + p50.reset_tipracks() + else: + protocol.pause("RESET p50 TIPS") + p50.reset_tipracks() + Resetcount += 1 + p50_tipracks_count += 1 + p50_tips = 0 + + def DispWasteVol(Vol): + global WasteVol + WasteVol += int(Vol) + if WasteVol < 1500: + Liquid_trash = Liquid_trash_well_1 + if WasteVol >= 1500 and WasteVol < 3000: + Liquid_trash = Liquid_trash_well_2 + if WasteVol >= 3000: + Liquid_trash = Liquid_trash_well_3 + + # CUSTOM OFFSETS + if DEFAULT_OFFSETS == False: + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": -2, "z": 0} + hs_pick_up_offset = {"x": 0, "y": -2, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 0.0, "z": 0.5} + mb_pick_up_offset = {"x": 0, "y": -2, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": 0, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + else: + # HEATERSHAKER OFFSETS + hs_drop_offset = {"x": 0, "y": 0, "z": 0} + hs_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # MAG BLOCK OFFSETS + mb_drop_offset = {"x": 0, "y": 0.0, "z": 0} + mb_pick_up_offset = {"x": 0, "y": 0, "z": 0} + # THERMOCYCLER OFFSETS + tc_drop_offset = {"x": 0, "y": 0, "z": 0} + tc_pick_up_offset = {"x": 0, "y": 0, "z": 0} + + ############################################################################################################################################ + ############################################################################################################################################ + ############################################################################################################################################ + # commands + for loop in range(RUN): + thermocycler.open_lid() + heatershaker.open_labware_latch() + if DRYRUN == False: + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + thermocycler.set_block_temperature(4) + thermocycler.set_lid_temperature(100) + temp_block.set_temperature(4) + protocol.pause("Ready") + heatershaker.close_labware_latch() + + Liquid_trash = Liquid_trash_well_1 + + if STEP_FXENZ == 1: + protocol.comment("==============================================") + protocol.comment("--> FX") + protocol.comment("==============================================") + + protocol.comment("--> Adding FX") + FXENZBuffVol = 10 + FXENZVMixRep = 3 if DRYRUN == False else 1 + FXENZVMixVol = 20 + FXENZBuffPremix = 2 if DRYRUN == False else 1 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck(50) + p50.pick_up_tip() + p50.mix(FXENZBuffPremix, FXENZBuffVol + 1, FXENZ.bottom(z=0.25), rate=0.25) + p50.aspirate(FXENZBuffVol + 1, FXENZ.bottom(z=0.25), rate=0.25) + p50.dispense(1, FXENZ.bottom(z=0.25), rate=0.25) + p50.dispense(FXENZBuffVol, sample_plate_1.wells_by_name()[X].bottom(z=0.25), rate=0.25) + p50.mix(FXENZVMixRep, FXENZVMixVol, rate=0.5) + p50.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(seconds=3) + p50.blow_out(sample_plate_1[X].top(z=-3)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + if STEP_FXDECK == 1: + ############################################################################################################################################ + thermocycler.close_lid() + if DRYRUN == False: + profile_FXENZ = [{"temperature": 32, "hold_time_minutes": FRAGTIME}, {"temperature": 65, "hold_time_minutes": 30}] + thermocycler.execute_profile(steps=profile_FXENZ, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(4) + ############################################################################################################################################ + thermocycler.open_lid() + + if STEP_LIG == 1: + protocol.comment("==============================================") + protocol.comment("--> Adapter Ligation") + protocol.comment("==============================================") + + protocol.comment("--> Adding Barcodes") + BarcodeVol = 5 + BarcodeMixRep = 3 if DRYRUN == False else 1 + BarcodeMixVol = 10 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck(50) + p50.pick_up_tip() + p50.aspirate(BarcodeVol + 1, reagent_plate_1.wells_by_name()[barcodes[loop]].bottom(z=0.25), rate=0.25) + p50.dispense(1, reagent_plate_1.wells_by_name()[barcodes[loop]].bottom(z=0.25), rate=0.25) + p50.dispense(BarcodeVol, sample_plate_1.wells_by_name()[X].bottom(z=0.25), rate=0.25) + p50.mix(BarcodeMixRep, BarcodeMixVol) + p50.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(seconds=3) + p50.blow_out(sample_plate_1[X].top(z=-3)) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + protocol.comment("--> Adding Lig") + LIGVol = 45 + LIGMixRep = 10 + LIGMixVol = 80 + LIGMixPremix = 2 if DRYRUN == False else 1 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.mix(LIGMixPremix, LIGVol + 2, LIG.bottom(z=0.25), rate=0.2) + p1000.aspirate(LIGVol + 2, LIG.bottom(z=0.25), rate=0.2) + p1000.dispense(2, LIG.bottom(z=0.25), rate=0.2) + p1000.default_speed = 5 + p1000.move_to(LIG.top(z=5)) + protocol.delay(seconds=1) + p1000.default_speed = 400 + p1000.dispense(LIGVol, sample_plate_1[X].bottom(z=0.25), rate=0.25) + p1000.move_to(sample_plate_1[X].bottom(z=0.3)) + p1000.mix(LIGMixRep, LIGMixVol, rate=0.5) + p1000.default_speed = 5 + p1000.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(seconds=3) + p1000.blow_out(sample_plate_1[X].top(z=-3)) + p1000.default_speed = 400 + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if STEP_LIGDECK == 1: + ############################################################################################################################################ + thermocycler.close_lid() + if DRYRUN == False: + profile_LIG = [{"temperature": 20, "hold_time_minutes": 15}] + thermocycler.execute_profile(steps=profile_LIG, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(10) + ############################################################################################################################################ + thermocycler.open_lid() + + if STEP_CLEANUP_1 == 1: + protocol.comment("==============================================") + protocol.comment("--> Cleanup 1") + protocol.comment("==============================================") + + protocol.comment("--> ADDING AMPure (0.8x)") + AMPureVol = 80 + SampleVol = 100 + AMPureMixRPM = 1600 + AMPureMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + AMPurePremix = 3 if DRYRUN == False else 1 + TransferSup = 100 + # =============================================== + for loop, X in enumerate(column_1_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.mix(AMPurePremix, AMPureVol + 3, AMPure.bottom(z=1), rate=0.25) + p1000.aspirate(AMPureVol + 3, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(3, AMPure.bottom(z=1), rate=0.25) + p1000.default_speed = 5 + p1000.move_to(AMPure.top(z=-3)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(AMPure.top().move(types.Point(x=4, z=-3))) + p1000.move_to(AMPure.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(AMPureVol, BeadPlate[column_A_list[loop]].bottom(z=0.25), rate=0.25) + protocol.comment("--> Transferring Samples") + # ================================ + p1000.move_to(sample_plate_1[X].bottom(z=0.3)) + p1000.aspirate(TransferSup / 2, rate=0.25) + protocol.delay(seconds=0.2) + p1000.move_to(sample_plate_1[X].bottom(z=0.1)) + p1000.aspirate(TransferSup / 2, rate=0.25) + p1000.dispense(TransferSup, BeadPlate[column_A_list[loop]].bottom(z=1)) + # ================================ + p1000.default_speed = 5 + p1000.move_to(BeadPlate[column_A_list[loop]].bottom(z=3)) + for Mix in range(2): + p1000.aspirate(70, rate=0.5) + p1000.move_to(BeadPlate[column_A_list[loop]].bottom(z=0.25)) + p1000.aspirate(70, rate=0.5) + p1000.dispense(70, rate=0.5) + p1000.move_to(BeadPlate[column_A_list[loop]].bottom(z=3)) + p1000.dispense(70, rate=0.5) + Mix += 1 + p1000.move_to(BeadPlate[column_A_list[loop]].top(z=-3)) + protocol.delay(seconds=1) + p1000.blow_out(BeadPlate[column_A_list[loop]].top(z=-3)) + p1000.touch_tip(speed=100) + p1000.default_speed = 400 + p1000.move_to(BeadPlate[column_A_list[loop]].top(z=5)) + p1000.move_to(BeadPlate[column_A_list[loop]].top(z=0)) + p1000.move_to(BeadPlate[column_A_list[loop]].top(z=5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=AMPureMixRPM) + protocol.delay(AMPureMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) HEATER SHAKER --> MAG BLOCK + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_A_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(z=3)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(seconds=3) + p1000.move_to(BeadPlate[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3), rate=0.5) + protocol.delay(seconds=1) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + # =============================================== + if ETOH_1_AirMultiDis == True: + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + for loop, X in enumerate(column_A_list): + p1000.aspirate(ETOHMaxVol, EtOH_1.bottom(z=1), rate=0.5) + p1000.move_to(EtOH_1.top(z=0)) + p1000.move_to(EtOH_1.top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(EtOH_1.top().move(types.Point(x=4, z=-3))) + p1000.move_to(EtOH_1.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(BeadPlate[X].top(z=2)) + p1000.dispense(ETOHMaxVol, rate=0.75) + protocol.delay(seconds=2) + p1000.blow_out(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.move_to(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + else: + for loop, X in enumerate(column_A_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(ETOHMaxVol, EtOH_1.bottom(z=1), rate=0.5) + p1000.move_to(EtOH_1.top(z=0)) + p1000.move_to(EtOH_1.top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(EtOH_1.top().move(types.Point(x=4, z=-3))) + p1000.move_to(EtOH_1.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(ETOHMaxVol, BeadPlate[X].top(z=-3), rate=0.5) + protocol.delay(seconds=2) + p1000.blow_out() + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.move_to(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_A_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_RemoveSup_Tip[loop]) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(z=2)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(seconds=3) + p1000.move_to(BeadPlate[X].bottom(z=0.25)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3)) + protocol.delay(seconds=2) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual Wash") + # =============================================== + for loop, X in enumerate(column_A_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_RemoveSup_Tip[loop]) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(0.1)) + p1000.aspirate(200, rate=0.25) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM MAG BLOCK --> HEATERSHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Adding RSB") + RSBVol = 50 + RSBMixRPM = 2000 + RSBMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + # =============================================== + if RSB_1_AirMultiDis == True: + if REUSE_TIPS == True: + p50.pick_up_tip(RSB_AirMultiDis_Tip) + else: + tipcheck(50) + p50.pick_up_tip() + for loop, X in enumerate(column_A_list): + p50.aspirate(RSBVol, RSB.bottom(z=1), rate=0.25) + p50.move_to(BeadPlate.wells_by_name()[X].top(z=3)) + p50.dispense(RSBVol, rate=0.75) + p50.blow_out(BeadPlate.wells_by_name()[X].top(z=3)) + if REUSE_TIPS == True: + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + else: + for loop, X in enumerate(column_A_list): + if REUSE_TIPS == True: + p50.pick_up_tip(RSB_AirMultiDis_Tip) + else: + tipcheck(50) + p50.pick_up_tip() + p50.aspirate(RSBVol, RSB.bottom(z=1), rate=0.25) + p50.move_to(BeadPlate.wells_by_name()[X].bottom(z=1)) + p50.dispense(RSBVol, BeadPlate.wells_by_name()[X].bottom(z=1), rate=0.5) + p50.blow_out(BeadPlate.wells_by_name()[X].top(z=-3)) + if REUSE_TIPS == True: + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=RSBMixRPM) + protocol.delay(RSBMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM HEATERSHAKER --> MAG BLOCK + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=3) + + protocol.comment("--> Transferring Supernatant") + TransferSup = 50 + # =============================================== + for loop, X in enumerate(column_A_list): + tipcheck(50) + p50.pick_up_tip() + p50.move_to(BeadPlate[X].bottom(z=0.5)) + p50.aspirate(TransferSup / 2, rate=0.25) + protocol.delay(seconds=1) + p50.move_to(BeadPlate[X].bottom(z=0.2)) + p50.aspirate(TransferSup / 2, rate=0.25) + p50.dispense(TransferSup, BeadPlate[column_B_list[loop]].bottom(z=1), rate=0.5) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM MAG BLOCK --> HEATERSHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + if STEP_CLEANUP_2 == 1: + protocol.comment("==============================================") + protocol.comment("--> Cleanup 2") + protocol.comment("==============================================") + + Liquid_trash = Liquid_trash_well_2 + + protocol.delay(seconds=3) + + protocol.comment("--> ADDING AMPure (0.8x)") + AMPureVol = 50 + SampleVol = 50 + AMPureMixRPM = 1600 + AMPureMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + AMPurePremix = 3 if DRYRUN == False else 1 + # =============================================== + for loop, X in enumerate(column_B_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.mix(AMPurePremix, AMPureVol + 3, AMPure.bottom(z=1), rate=0.25) + p1000.aspirate(AMPureVol + 3, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(3, AMPure.bottom(z=1), rate=0.25) + p1000.default_speed = 5 + p1000.move_to(AMPure.top(z=-3)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(AMPure.top().move(types.Point(x=4, z=-3))) + p1000.move_to(AMPure.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(AMPureVol, BeadPlate[X].bottom(z=0.25), rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].bottom(z=3)) + for Mix in range(2): + p1000.aspirate(70, rate=0.5) + p1000.move_to(BeadPlate[X].bottom(z=0.25)) + p1000.aspirate(20, rate=0.5) + p1000.dispense(20, rate=0.5) + p1000.move_to(BeadPlate[X].bottom(z=3)) + p1000.dispense(70, rate=0.5) + Mix += 1 + p1000.move_to(BeadPlate[X].top(z=-3)) + protocol.delay(seconds=1) + p1000.blow_out(BeadPlate[X].top(z=-3)) + p1000.touch_tip(speed=100) + p1000.default_speed = 400 + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.move_to(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=AMPureMixRPM) + protocol.delay(AMPureMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) HEATER SHAKER --> MAG BLOCK + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_B_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(z=3)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(seconds=3) + p1000.move_to(BeadPlate[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3), rate=0.5) + protocol.delay(seconds=1) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + # =============================================== + if ETOH_2_AirMultiDis == True: + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + for loop, X in enumerate(column_B_list): + p1000.aspirate(ETOHMaxVol, EtOH_2.bottom(z=1), rate=0.5) + p1000.move_to(EtOH_2.top(z=0)) + p1000.move_to(EtOH_2.top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(EtOH_2.top().move(types.Point(x=4, z=-3))) + p1000.move_to(EtOH_2.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(BeadPlate[X].top(z=2)) + p1000.dispense(ETOHMaxVol, rate=0.75) + protocol.delay(seconds=2) + p1000.blow_out(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.move_to(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + else: + for loop, X in enumerate(column_B_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(ETOHMaxVol, EtOH_2.bottom(z=1), rate=0.5) + p1000.move_to(EtOH_2.top(z=0)) + p1000.move_to(EtOH_2.top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(EtOH_2.top().move(types.Point(x=4, z=-3))) + p1000.move_to(EtOH_2.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(ETOHMaxVol, BeadPlate[X].top(z=-3), rate=0.5) + protocol.delay(seconds=2) + p1000.blow_out() + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.move_to(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_B_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_RemoveSup_Tip[loop]) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(z=2)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(seconds=3) + p1000.move_to(BeadPlate[X].bottom(z=0.25)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3)) + protocol.delay(seconds=2) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual Wash") + # =============================================== + for loop, X in enumerate(column_B_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_RemoveSup_Tip[loop]) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(0.1)) + p1000.aspirate(200, rate=0.25) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM MAG BLOCK --> HEATERSHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Adding RSB") + RSBVol = 22 + RSBMixRPM = 2000 + RSBMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + # =============================================== + if RSB_2_AirMultiDis == True: + if REUSE_TIPS == True: + p50.pick_up_tip(RSB_AirMultiDis_Tip) + else: + tipcheck(50) + p50.pick_up_tip() + for loop, X in enumerate(column_B_list): + p50.aspirate(RSBVol, RSB.bottom(z=1), rate=0.25) + p50.move_to(BeadPlate.wells_by_name()[X].top(z=3)) + p50.dispense(RSBVol, rate=0.75) + p50.blow_out(BeadPlate.wells_by_name()[X].top(z=3)) + if REUSE_TIPS == True: + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + else: + for loop, X in enumerate(column_B_list): + if REUSE_TIPS == True: + p50.pick_up_tip(RSB_AirMultiDis_Tip) + else: + tipcheck(50) + p50.pick_up_tip() + p50.aspirate(RSBVol, RSB.bottom(z=1), rate=0.25) + p50.move_to(BeadPlate.wells_by_name()[X].bottom(z=1)) + p50.dispense(RSBVol, BeadPlate.wells_by_name()[X].bottom(z=1), rate=0.5) + p50.blow_out(BeadPlate.wells_by_name()[X].top(z=-3)) + if REUSE_TIPS == True: + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=RSBMixRPM) + protocol.delay(RSBMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM HEATERSHAKER --> MAG BLOCK + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=3) + + if STEP_PCR == 1: + protocol.comment("==============================================") + protocol.comment("--> Amplification") + protocol.comment("==============================================") + + protocol.comment("--> Adding Primer") + PrimerVol = 5 + PrimerMixRep = 2 + PrimerMixVol = 10 + TransferSup = 20 + # =============================================== + for loop, X in enumerate(column_B_list): + tipcheck(50) + p50.pick_up_tip() + p50.aspirate(PrimerVol, Primer.bottom(z=0.5), rate=0.25) + p50.dispense(PrimerVol, sample_plate_1.wells_by_name()[column_2_list[loop]].bottom(z=1), rate=0.25) + + protocol.comment("--> Transferring Supernatant") + p50.move_to(BeadPlate[X].bottom(z=0.5)) + p50.aspirate(TransferSup / 2, rate=0.25) + protocol.delay(seconds=1) + p50.move_to(BeadPlate[X].bottom(z=0.2)) + p50.aspirate(TransferSup / 2, rate=0.25) + p50.dispense(TransferSup, sample_plate_1[column_2_list[loop]].bottom(z=1), rate=0.5) + + p50.mix(PrimerMixRep, PrimerMixVol, rate=0.5) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + protocol.comment("--> Adding PCR") + PCRVol = 25 + PCRMixRep = 10 + PCRMixVol = 45 + # =============================================== + for loop, X in enumerate(column_2_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.mix(2, PCRVol, PCR.bottom(z=0.5), rate=0.25) + p1000.aspirate(PCRVol, PCR.bottom(z=0.5), rate=0.25) + p1000.dispense(PCRVol, sample_plate_1[X].bottom(z=1), rate=0.25) + p1000.mix(PCRMixRep, PCRMixVol, rate=0.5) + p1000.move_to(sample_plate_1[X].top(z=-3)) + protocol.delay(seconds=3) + p1000.blow_out(sample_plate_1[X].top(z=-3)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if STEP_PCRDECK == 1: + ############################################################################################################################################ + thermocycler.close_lid() + if DRYRUN == False: + profile_PCR_1 = [{"temperature": 98, "hold_time_seconds": 45}] + thermocycler.execute_profile(steps=profile_PCR_1, repetitions=1, block_max_volume=50) + profile_PCR_2 = [ + {"temperature": 98, "hold_time_seconds": 15}, + {"temperature": 60, "hold_time_seconds": 30}, + {"temperature": 72, "hold_time_seconds": 30}, + ] + thermocycler.execute_profile(steps=profile_PCR_2, repetitions=PCRCYCLES, block_max_volume=50) + profile_PCR_3 = [{"temperature": 72, "hold_time_minutes": 1}] + thermocycler.execute_profile(steps=profile_PCR_3, repetitions=1, block_max_volume=50) + thermocycler.set_block_temperature(4) + thermocycler.open_lid() + ############################################################################################################################################ + + if STEP_CLEANUP_3 == 1: + protocol.comment("==============================================") + protocol.comment("--> Cleanup 3") + protocol.comment("==============================================") + + Liquid_trash = Liquid_trash_well_3 + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM MAG BLOCK --> HEATERSHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> ADDING AMPure (0.8x)") + AMPureVol = 50 + SampleVol = 50 + AMPureMixRPM = 1600 + AMPureMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + AMPurePremix = 3 if DRYRUN == False else 1 + TransferSup = 50 + # =============================================== + for loop, X in enumerate(column_C_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.mix(AMPurePremix, AMPureVol + 3, AMPure.bottom(z=1), rate=0.25) + p1000.aspirate(AMPureVol + 3, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(3, AMPure.bottom(z=1), rate=0.25) + p1000.default_speed = 5 + p1000.move_to(AMPure.top(z=-3)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(AMPure.top().move(types.Point(x=4, z=-3))) + p1000.move_to(AMPure.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(AMPureVol, BeadPlate[X].bottom(z=0.25), rate=0.25) + # =====Transferring Samples======= + protocol.comment("--> Transferring Samples") + p1000.move_to(sample_plate_1[column_2_list[loop]].bottom(z=0.25)) + p1000.aspirate(TransferSup / 2, rate=0.25) + protocol.delay(seconds=0.2) + p1000.move_to(sample_plate_1[column_2_list[loop]].bottom(z=0.1)) + p1000.aspirate(TransferSup / 2, rate=0.25) + p1000.dispense(TransferSup, BeadPlate[X].bottom(z=1)) + # ================================ + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].bottom(z=3)) + for Mix in range(2): + p1000.aspirate(70, rate=0.5) + p1000.move_to(BeadPlate[X].bottom(z=0.25)) + p1000.aspirate(20, rate=0.5) + p1000.dispense(20, rate=0.5) + p1000.move_to(BeadPlate[X].bottom(z=3)) + p1000.dispense(70, rate=0.5) + Mix += 1 + p1000.move_to(BeadPlate[X].top(z=-3)) + protocol.delay(seconds=1) + p1000.blow_out(BeadPlate[X].top(z=-3)) + p1000.touch_tip(speed=100) + p1000.default_speed = 400 + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.move_to(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=AMPureMixRPM) + protocol.delay(AMPureMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) HEATER SHAKER --> MAG BLOCK + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_C_list): + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(z=3)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(seconds=3) + p1000.move_to(BeadPlate[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3), rate=0.5) + protocol.delay(seconds=1) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + for X in range(2): + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + # =============================================== + if ETOH_3_AirMultiDis == True: + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + for loop, X in enumerate(column_C_list): + p1000.aspirate(ETOHMaxVol, EtOH_3.bottom(z=1), rate=0.5) + p1000.move_to(EtOH_3.top(z=0)) + p1000.move_to(EtOH_3.top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(EtOH_3.top().move(types.Point(x=4, z=-3))) + p1000.move_to(EtOH_3.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(BeadPlate[X].top(z=2)) + p1000.dispense(ETOHMaxVol, rate=0.75) + protocol.delay(seconds=2) + p1000.blow_out(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.move_to(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + else: + for loop, X in enumerate(column_C_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(ETOHMaxVol, EtOH_3.bottom(z=1), rate=0.5) + p1000.move_to(EtOH_3.top(z=0)) + p1000.move_to(EtOH_3.top(z=-5)) + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(EtOH_3.top().move(types.Point(x=4, z=-3))) + p1000.move_to(EtOH_3.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.dispense(ETOHMaxVol, BeadPlate[X].top(z=-3), rate=0.5) + protocol.delay(seconds=2) + p1000.blow_out() + p1000.move_to(BeadPlate[X].top(z=5)) + p1000.move_to(BeadPlate[X].top(z=0)) + p1000.move_to(BeadPlate[X].top(z=5)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_C_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_RemoveSup_Tip[loop]) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(z=2)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(seconds=3) + p1000.move_to(BeadPlate[X].bottom(z=0.25)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3)) + protocol.delay(seconds=2) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=1) + + protocol.comment("--> Removing Residual Wash") + # =============================================== + for loop, X in enumerate(column_C_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(ETOH_RemoveSup_Tip[loop]) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(0.1)) + p1000.aspirate(50, rate=0.25) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM MAG BLOCK --> HEATERSHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Adding RSB") + RSBVol = 32 + RSBMixRPM = 2000 + RSBMixTime = 5 * 60 if DRYRUN == False else 0.1 * 60 + # =============================================== + if RSB_3_AirMultiDis == True: + if REUSE_TIPS == True: + p50.pick_up_tip(RSB_AirMultiDis_Tip) + else: + tipcheck(50) + p50.pick_up_tip() + for loop, X in enumerate(column_C_list): + p50.aspirate(RSBVol, RSB.bottom(z=1), rate=0.25) + p50.move_to(BeadPlate.wells_by_name()[X].top(z=3)) + p50.dispense(RSBVol, rate=0.75) + p50.blow_out(BeadPlate.wells_by_name()[X].top(z=3)) + if REUSE_TIPS == True: + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + else: + for loop, X in enumerate(column_C_list): + if REUSE_TIPS == True: + p50.pick_up_tip(RSB_AirMultiDis_Tip) + else: + tipcheck(50) + p50.pick_up_tip() + p50.aspirate(RSBVol, RSB.bottom(z=1), rate=0.25) + p50.move_to(BeadPlate.wells_by_name()[X].bottom(z=1)) + p50.dispense(RSBVol, BeadPlate.wells_by_name()[X].bottom(z=1), rate=0.5) + p50.blow_out(BeadPlate.wells_by_name()[X].top(z=-3)) + if REUSE_TIPS == True: + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=RSBMixRPM) + protocol.delay(RSBMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM HEATERSHAKER --> MAG BLOCK + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + if DRYRUN == False: + thermocycler.set_block_temperature(55) + + if DRYRUN == False: + protocol.delay(minutes=3) + + if STEP_NORMALIZE == 1: + protocol.comment("==============================================") + protocol.comment("--> Normalization") + protocol.comment("==============================================") + + protocol.comment("--> Dispensing NormWash") + NormWashVol = 200 + # =============================================== + if NWASH_AirMultiDis == True: + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + for loop, X in enumerate(column_6_list): + p1000.aspirate(NormWashVol, NormWash.bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, sample_plate_1[X].bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, sample_plate_1[X].top(z=0), rate=0.5) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + else: + for loop, X in enumerate(column_6_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(NormWashVol, NormWash.bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, sample_plate_1[X].bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, sample_plate_1[X].top(z=0), rate=0.5) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + protocol.comment("--> Dispensing NormBeads") + NormBeadVol = 5 + NormBeadPremix = 10 if DRYRUN == False else 1 + # =============================================== + tipcheck(50) + p50.pick_up_tip() + p50.mix(NormBeadPremix, (5 * COLUMNS) + 3, NormBead.bottom(z=1), rate=0.5) + p50.aspirate((5 * COLUMNS) + 3, NormBead.bottom(z=1), rate=0.5) + for loop, X in enumerate(column_8_list): + p50.dispense(NormBeadVol, BeadPlate[X].bottom(z=1), rate=0.5) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + protocol.comment("--> Transferring Supernatant") + TransferSup = 30 + # =============================================== + for loop, X in enumerate(column_C_list): + tipcheck(50) + p50.pick_up_tip() + p50.move_to(sample_plate_1[X].bottom(z=0.25)) + # Transferring 1/2 to Normalization Sample Wells + p50.aspirate(TransferSup / 2, rate=0.25) + p50.dispense(TransferSup / 2, BeadPlate[column_8_list[loop]].bottom(z=1), rate=0.5) + p50.move_to(sample_plate_1[X].bottom(z=0.2)) + # Transferring 1/2 to Excess Sample Wells + p50.aspirate(TransferSup / 2, rate=0.25) + p50.dispense(TransferSup / 2, reagent_plate_1[column_7_list[loop]].bottom(z=1), rate=0.5) + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM MAG BLOCK --> HEATERSHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Adding NormWash 1") + NormWashVol = 180 + # =============================================== + if NWASH_AirMultiDis == True: + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + for loop, X in enumerate(column_6_list): + p1000.move_to(sample_plate_1[X].bottom(z=5)) + p1000.aspirate(NormWashVol / 2, rate=0.25) + protocol.delay(seconds=0.2) + p1000.move_to(sample_plate_1[X].bottom(z=0.25)) + p1000.aspirate(NormWashVol / 2, rate=0.25) + p1000.dispense(NormWashVol, BeadPlate[column_8_list[loop]].bottom(z=1)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + else: + for loop, X in enumerate(column_6_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=5)) + p1000.aspirate(NormWashVol / 2, rate=0.25) + protocol.delay(seconds=0.2) + p1000.move_to(sample_plate_1[X].bottom(z=0.25)) + p1000.aspirate(NormWashVol / 2, rate=0.25) + p1000.dispense(NormWashVol, BeadPlate[column_8_list[loop]].bottom(z=1)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + protocol.comment("--> Dispensing NormWash") + NormWashVol = 200 + # =============================================== + if NWASH_AirMultiDis == True: + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + for loop, X in enumerate(column_6_list): + p1000.aspirate(NormWashVol, NormWash.bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, BeadPlate[X].bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, BeadPlate[X].top(z=0), rate=0.5) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + else: + for loop, X in enumerate(column_6_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(NormWashVol, NormWash.bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, BeadPlate[X].bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, BeadPlate[X].top(z=0), rate=0.5) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=3) + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) HEATER SHAKER --> MAG BLOCK + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Remove Bead Wash") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_8_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_Tip[loop]) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(z=3)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(BeadPlate[X].bottom(z=0.25)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3), rate=0.75) + protocol.delay(minutes=0.1) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM MAG BLOCK --> HEATERSHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Adding NormWash 2") + NormWashVol = 200 + # =============================================== + if NWASH_AirMultiDis == True: + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + for loop, X in enumerate(column_6_list): + p1000.aspirate(NormWashVol, NormWash.bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, BeadPlate[X].bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, BeadPlate[X].top(z=0), rate=0.5) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + else: + for loop, X in enumerate(column_6_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_AirMultiDis_Tip) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.aspirate(NormWashVol, NormWash.bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, BeadPlate[X].bottom(z=1), rate=0.5) + p1000.dispense(NormWashVol / 2, BeadPlate[X].top(z=0), rate=0.5) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) HEATER SHAKER --> MAG BLOCK + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Remove Bead Wash") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_8_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_Tip[loop]) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(z=3)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(BeadPlate[X].bottom(z=0.25)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3), rate=0.75) + protocol.delay(minutes=0.1) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + protocol.comment("--> Remove Residual Bead Wash") + RemoveSup = 200 + # =============================================== + for loop, X in enumerate(column_8_list): + if REUSE_TIPS == True: + p1000.pick_up_tip(NWASH_Tip[loop]) + else: + tipcheck(200) + p1000.pick_up_tip() + p1000.move_to(BeadPlate[X].bottom(z=1)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(BeadPlate[X].bottom(z=0.25)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(BeadPlate[X].top(z=-2)) + p1000.default_speed = 200 + p1000.touch_tip(speed=100) + p1000.dispense(200, Liquid_trash.top(z=-3), rate=0.75) + protocol.delay(minutes=0.1) + p1000.blow_out() + # =====Reservoir Tip Touch======== + p1000.default_speed = 100 + p1000.move_to(Liquid_trash.top().move(types.Point(x=4, z=-3))) + p1000.move_to(Liquid_trash.top().move(types.Point(x=-4, z=-3))) + p1000.default_speed = 400 + # ================================ + p1000.move_to(Liquid_trash.top(z=-5)) + p1000.move_to(Liquid_trash.top(z=0)) + if REUSE_TIPS == True: + p1000.return_tip() + else: + p1000.return_tip() if TIP_TRASH == False else p1000.drop_tip() + p200_tips += 1 + # =============================================== + + if DRYRUN == False: + protocol.delay(minutes=0.5) + + # ============================================================================================ + # GRIPPER MOVE BeadPlate FROM MAG BLOCK TO HEATER SHAKER + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=hs_adapter, + use_gripper=USE_GRIPPER, + pick_up_offset=mb_pick_up_offset, + drop_offset=hs_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + protocol.comment("--> Adding NormElute") + NormEluteVol = 25 + NormEluteMixRPM = 2000 + NormEluteMixTime = 2 * 60 if DRYRUN == False else 0.1 * 60 + # =============================================== + for loop, X in enumerate(column_8_list): + if REUSE_TIPS == True: + p50.pick_up_tip(NELUTE_Tip[loop]) + else: + tipcheck(50) + p50.pick_up_tip() + p50.aspirate(NormEluteVol, NormElute.bottom(z=1), rate=0.25) + p50.move_to(BeadPlate.wells_by_name()[X].bottom(z=1)) + p50.dispense(NormEluteVol, rate=0.5) + p50.blow_out(BeadPlate.wells_by_name()[X].top()) + if REUSE_TIPS == True: + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + heatershaker.set_and_wait_for_shake_speed(rpm=NormEluteMixRPM) + protocol.delay(NormEluteMixTime) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE (BeadPlate) FROM HEATERSHAKER --> MAG BLOCK + heatershaker.open_labware_latch() + protocol.move_labware( + labware=BeadPlate, + new_location=mag_block, + use_gripper=USE_GRIPPER, + pick_up_offset=hs_pick_up_offset, + drop_offset=mb_drop_offset, + ) + heatershaker.close_labware_latch() + # ============================================================================================ + + if DRYRUN == False: + protocol.delay(minutes=3) + + protocol.comment("--> Transferring Supernatant") + TransferSup = 22 + # =============================================== + for loop, X in enumerate(column_8_list): + if REUSE_TIPS == True: + p50.pick_up_tip(NELUTE_Tip[loop]) + else: + tipcheck(50) + p50.pick_up_tip() + p50.move_to(BeadPlate[X].bottom(z=0.2)) + p50.aspirate(TransferSup, rate=0.25) + p50.dispense(TransferSup, sample_plate_1[column_9_list[loop]].bottom(z=1)) + if REUSE_TIPS == True: + p50.return_tip() + else: + p50.return_tip() if TIP_TRASH == False else p50.drop_tip() + p50_tips += 1 + # =============================================== + + if ABR_TEST == True: + protocol.comment("==============================================") + protocol.comment("--> Resetting Run") + protocol.comment("==============================================") + + heatershaker.open_labware_latch() + if DEACTIVATE_TEMP == True: + thermocycler.deactivate_block() + thermocycler.deactivate_lid() + temp_block.deactivate() + protocol.comment("Number of Resets: " + str(Resetcount)) + protocol.comment("Number of p200 Tips Used: " + str(p200_tips + (12 * p200_tipracks * p200_tipracks_count))) + protocol.comment("Number of p50 Tips Used: " + str(p50_tips + (12 * p50_tipracks * p50_tipracks_count))) diff --git a/app-testing/files/protocols/pl_SamplePrep_MS_Cleanup_Flex_upto96.py b/app-testing/files/protocols/pl_SamplePrep_MS_Cleanup_Flex_upto96.py new file mode 100644 index 00000000000..c0d2cf9e780 --- /dev/null +++ b/app-testing/files/protocols/pl_SamplePrep_MS_Cleanup_Flex_upto96.py @@ -0,0 +1,268 @@ +def get_values(*names): + import json + + _all_values = json.loads("""{"NUM_SAMPLES":96,"PIPET_LOCATION":1,"protocol_filename":"SamplePrep_MS_Cleanup_Flex_upto96"}""") + return [_all_values[n] for n in names] + + +from opentrons.types import Point + +metadata = {"protocolName": "Digested Sample Clean-up for LC/MS - Flex w/ Thermal Cycler", "author": "Boren Lin, Opentrons", "source": ""} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + +######################## + +NUM_SAMPLES = 96 +# max. 96 + +CLEANUP_VOL = 55 +SP3_VOL = 10 +ACN_VOL_1 = 1292 +ACN_VOL_2 = 1000 +DMSO_VOL = 80 + +PIPET_LOCATION = 1 +# P1000 8-ch at Left: 1; Right :2 + +USE_GRIPPER = True + +######################### + +try: + [NUM_SAMPLES, PIPET_LOCATION] = get_values("NUM_SAMPLES", "PIPET_LOCATION") + +except NameError: + # get_values is not defined, so proceed with defaults + pass + +global num_col +global m1000_loc + +num_col = int(NUM_SAMPLES // 8) +if NUM_SAMPLES % 8 != 0: + num_col = num_col + 1 + +if PIPET_LOCATION == 1: + m1000_loc = "Left" +else: + m1000_loc = "Right" + + +def run(ctx): + + # load labware + tc = ctx.load_module("thermocycler module gen2") + sample_digested_plate = tc.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "DIGESTED SAMPLES") + + hs = ctx.load_module("heaterShakerModuleV1", "D1") + hs_adapter = hs.load_adapter("opentrons_96_deep_well_adapter") + working_plate = hs_adapter.load_labware("nest_96_wellplate_2ml_deep", "WORKING PLATE - CLEAN-UP") + + acn_stock = ctx.load_labware("nest_1_reservoir_290ml", "C1", "ACN") + waste_res = ctx.load_labware("nest_1_reservoir_290ml", "D2", "LIQUID WASTE") + mag = ctx.load_module("magneticBlockV1", "C2") + reagent_res = ctx.load_labware("nest_96_wellplate_2ml_deep", "C3", "REAGENTS") + # beads, DMSO 2% + final_plate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "D3", "FINAL PRODUCTS") + + tips_200_sample = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B3", "200uL TIPS") + tip_loc_sample = tips_200_sample.wells()[:96] + tips_200_elution = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "C4", "200uL TIPS") + tip_loc_elution = tips_200_elution.wells()[:96] + + tips_200_reagent = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A3", "200uL TIPS") + tip_loc_reagent = tips_200_reagent.wells()[:96] + + tips_1000_wash1 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B2", "1000uL TIPS") + tip_loc_wash1 = tips_1000_wash1.wells()[:96] + tips_1000_wash2 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "1000uL TIPS") + tip_loc_wash2 = tips_1000_wash2.wells()[:96] + + m1000 = ctx.load_instrument("flex_8channel_1000", m1000_loc) + + # assign locations + sample_digested = sample_digested_plate.rows()[0][:num_col] + + sp3 = reagent_res.rows()[0][0] + dmso = reagent_res.rows()[0][1] + acn = acn_stock.wells()[0] + waste = waste_res.wells()[0] + working = working_plate.rows()[0][:num_col] + final = final_plate.rows()[0][:NUM_SAMPLES] + + # liquid info and deck payout + vol_sample = 130 + def_sample = ctx.define_liquid( + name="DIGESTED SAMPLES", description="Digested Protein samples, volume per well", display_color="#FF0000" + ) ## Red + for p in range(NUM_SAMPLES): + sample_digested_plate.wells()[p].load_liquid(liquid=def_sample, volume=vol_sample / NUM_SAMPLES) + + vol_sp3 = (SP3_VOL * num_col) + 20 + vol_dmso = (DMSO_VOL * num_col) + 20 + def_sp3 = ctx.define_liquid( + name="BEAD SLURRY", description="50 ug/uL Magnetic Beads for SP3 in MS grade water, volume per well", display_color="#0000FF" + ) ## Blue + def_dmso = ctx.define_liquid(name="DMSO", description="2% DMSO in MS grade water, volume per well", display_color="#FFB6C1") ## Pink + for p in range(8): + reagent_res.rows()[p][0].load_liquid(liquid=def_sp3, volume=vol_sp3 / 8) + reagent_res.rows()[p][1].load_liquid(liquid=def_dmso, volume=vol_dmso / 8) + + vol_acn = (ACN_VOL_1 + ACN_VOL_2) * num_col * 8 + 24000 + def_acn = ctx.define_liquid(name="ACN", description="MS grade Acetonitrile", display_color="#E5E4E2") ## Gray + acn_stock.wells()[0].load_liquid(liquid=def_acn, volume=vol_acn) + + # protocol + + tc.open_lid() + + ctx.pause("Place WORKING PLATE - DIGESTION in Thermal Cycler") + ctx.pause("Load ACN Reservoir on Slot C1 and Reagent Plate on Slot C3") + + hs.open_labware_latch() + ctx.pause("Load WORKING PLATE - CLEAN-UP on Heater Shaker") + hs.close_labware_latch() + + ## prepare samples for SP3 clean-up + for n in range(num_col): + start = sample_digested[n] + end = working[n] + m1000.pick_up_tip(tip_loc_sample[n * 8]) + m1000.mix(3, CLEANUP_VOL * 0.75, start.bottom(z=0.2)) + m1000.aspirate(CLEANUP_VOL, start.bottom(z=0.2)) + ctx.delay(seconds=3) + m1000.air_gap(10) + m1000.dispense(10, end.top(z=0)) + m1000.dispense(CLEANUP_VOL, end.bottom(z=10)) + ctx.delay(seconds=3) + m1000.blow_out() + m1000.return_tip() + + ctx.move_labware( + labware=tips_200_sample, + new_location="B4", + use_gripper=USE_GRIPPER, + ) + + ctx.move_labware( + labware=tips_200_elution, + new_location="B3", + use_gripper=USE_GRIPPER, + ) + + ## add SP3 + total_vol = CLEANUP_VOL + + m1000.pick_up_tip(tip_loc_reagent[0]) + for n in range(num_col): + end_loc = working[n] + m1000.mix(5, SP3_VOL * (num_col - n), sp3.bottom(z=0.2)) + m1000.aspirate(SP3_VOL, sp3.bottom(z=0.2)) + ctx.delay(seconds=3) + m1000.air_gap(10) + m1000.dispense(10, end_loc.top(z=0)) + m1000.dispense(SP3_VOL, end_loc.top(z=-5)) + m1000.blow_out() + m1000.return_tip() + + total_vol = total_vol + SP3_VOL + + ## add ACN and discard + acn_vol = [ACN_VOL_1, ACN_VOL_2] + for wash in range(2): + x = int(acn_vol[wash] // 750) + if wash == 0: + m1000.pick_up_tip(tip_loc_wash1[0]) + else: + m1000.pick_up_tip(tip_loc_wash2[0]) + for _ in range(x): + for n in range(num_col): + start_loc = acn.bottom(z=0.2).move(Point(x=n * 9 - 49.5)) + end_loc = working[n] + m1000.aspirate(750, start_loc) + m1000.air_gap(20) + m1000.dispense(750 + 20, end_loc.top(z=-5)) + m1000.blow_out() + if acn_vol[wash] % 750 != 0: + for n in range(num_col): + start_loc = acn.bottom(z=0.2).move(Point(x=n * 9 - 49.5)) + end_loc = working[n] + m1000.aspirate(acn_vol[wash] - (750 * x), start_loc) + m1000.air_gap(20) + m1000.dispense(acn_vol[wash] - (750 * x) + 20, end_loc.top(z=-5)) + m1000.return_tip() + + hs.set_and_wait_for_shake_speed(rpm=500) + ctx.delay(seconds=10) + hs.set_and_wait_for_shake_speed(rpm=1500) + ctx.delay(seconds=10) + hs.set_and_wait_for_shake_speed(rpm=750) + ctx.delay(seconds=40) + hs.deactivate_shaker() + + hs.open_labware_latch() + ctx.move_labware( + labware=working_plate, + new_location=mag, + use_gripper=USE_GRIPPER, + ) + ctx.delay(minutes=3) + + total_vol = total_vol + acn_vol[wash] + + x = int(total_vol // 750) + if total_vol % 750 != 0: + x = x + 1 + for _ in range(x): + for n in range(num_col): + if wash == 0: + m1000.pick_up_tip(tip_loc_wash1[n * 8]) + else: + m1000.pick_up_tip(tip_loc_wash2[n * 8]) + start_loc = working[n] + m1000.aspirate(750, start_loc.bottom(z=0.2)) + m1000.air_gap(20) + m1000.dispense(750 + 20, waste.top(z=-5)) + m1000.blow_out() + m1000.return_tip() + + total_vol = 0 + + ctx.move_labware(labware=working_plate, new_location=hs_adapter, use_gripper=USE_GRIPPER) + hs.close_labware_latch() + + ctx.delay(minutes=2) + + ## add 2% DMSO + m1000.pick_up_tip(tip_loc_reagent[8]) + for n in range(num_col): + end_loc = working[n] + m1000.aspirate(DMSO_VOL, dmso.bottom(z=0.2)) + ctx.delay(seconds=3) + m1000.air_gap(10) + m1000.dispense(DMSO_VOL + 10, end_loc.bottom(z=20)) + m1000.blow_out() + m1000.return_tip() + + hs.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay(seconds=60) + hs.deactivate_shaker() + + ## transfer final product + hs.open_labware_latch() + ctx.move_labware(labware=working_plate, new_location=mag, use_gripper=USE_GRIPPER) + ctx.delay(minutes=3) + + for n in range(num_col): + start = working[n] + end = final[n] + m1000.pick_up_tip(tip_loc_elution[n * 8]) + m1000.aspirate(DMSO_VOL * 1.1, start.bottom(z=0.2)) + ctx.delay(seconds=3) + m1000.air_gap(10) + m1000.dispense(DMSO_VOL * 1.1 + 10, end.top(z=0)) + m1000.return_tip() diff --git a/app-testing/files/protocols/pl_SamplePrep_MS_Digest_Flex_upto96.py b/app-testing/files/protocols/pl_SamplePrep_MS_Digest_Flex_upto96.py new file mode 100644 index 00000000000..68bb5d1d9c1 --- /dev/null +++ b/app-testing/files/protocols/pl_SamplePrep_MS_Digest_Flex_upto96.py @@ -0,0 +1,276 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"NUM_SAMPLES":96,"LABWARE_SAMPLE":24,"PIPET_LOCATION":1,"DIGESTION_TIME_PRESET":1,"DIGESTION_TIME":24,"protocol_filename":"SamplePrep_MS_Digest_Flex_upto96"}""" + ) + return [_all_values[n] for n in names] + + +metadata = {"protocolName": "Trypsin Digestion for LC/MS - Flex w/ Thermal Cycler", "author": "Boren Lin, Opentrons", "source": ""} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + +######################## + +NUM_SAMPLES = 96 +# max. 96 + +LABWARE_SAMPLE = 96 +# 24 for 24 tube rack or 96 for 96 well plate + +SAMPLE_VOL = 50 +ABC_VOL = 50 +DTT_VOL = 10 +IAA_VOL = 10 +TRYPSIN_VOL = 10 + +DTT_TEMP = 55 +DTT_TIME = 30 +IAA_TEMP = 22 +IAA_TIME = 30 +TRYPSIN_TEMP = 37 + +PIPET_LOCATION = 1 +# P1000 8-ch at Left: 1; Right :2 + +DIGESTION_TIME_PRESET = 0 +# Yes:1; No:0 +# if Yes, +DIGESTION_TIME = 4 +# hours + +######################### + +try: + [NUM_SAMPLES, LABWARE_SAMPLE, PIPET_LOCATION, DIGESTION_TIME_PRESET, DIGESTION_TIME] = get_values( + "NUM_SAMPLES", "LABWARE_SAMPLE", "PIPET_LOCATION", "DIGESTION_TIME_PRESET", "DIGESTION_TIME" + ) + +except NameError: + # get_values is not defined, so proceed with defaults + pass + +global num_col +global m1000_loc +global s1000_loc + +num_col = int(NUM_SAMPLES // 8) +if NUM_SAMPLES % 8 != 0: + num_col = num_col + 1 + +if PIPET_LOCATION == 1: + m1000_loc = "Left" + s1000_loc = "Right" +else: + m1000_loc = "Right" + s1000_loc = "Left" + + +def run(ctx): + + # desk setup + tc = ctx.load_module("thermocycler module gen2") + sample_digested_plate = tc.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "WORKING PLATE - DIGESTION") + reagent_res = ctx.load_labware("nest_96_wellplate_2ml_deep", "D3", "REAGENTS") + # ABC, DTT, IAA, Trypsin + + tips_200_reused = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B3", "200uL TIPS, REUSED") + tip_loc_reused = tips_200_reused.wells()[:96] + tips_200 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "C3", "200uL TIPS") + tip_loc = tips_200.wells()[:96] + m1000 = ctx.load_instrument("flex_8channel_1000", m1000_loc) + if LABWARE_SAMPLE == 24: + s1000 = ctx.load_instrument("flex_1channel_1000", s1000_loc) + + # assign locations + abc = reagent_res.rows()[0][0] + dtt = reagent_res.rows()[0][1] + iaa = reagent_res.rows()[0][2] + trypsin = reagent_res.rows()[0][3] + sample_digested_in_col = sample_digested_plate.rows()[0][:num_col] + sample_digested_in_well = sample_digested_plate.wells()[:NUM_SAMPLES] + + # liquid info and deck payout + vol_abc = (ABC_VOL * num_col) + 20 + vol_dtt = (DTT_VOL * num_col) + 20 + vol_iaa = (IAA_VOL * num_col) + 20 + vol_trypsin = (TRYPSIN_VOL * num_col) + 20 + + def_abc = ctx.define_liquid(name="ABC", description="100 mM ABC in MS grade water, volume per well", display_color="#704848") ## Brown + def_dtt = ctx.define_liquid( + name="DTT", description="60 mM DTT in MS grade water, volume per well", display_color="#00FFF2" + ) ## Light Blue + def_iaa = ctx.define_liquid(name="IAA", description="187.5 mM IAA in 100 mM ABC, volume per well", display_color="#FFA500") ## Orange + def_trypsin = ctx.define_liquid( + name="TRYPSIN", description="0.2 ug/uL Trypsin in MS grade water, volume per well", display_color="#0EFF00" + ) ## Green + + for p in range(8): + reagent_res.rows()[p][0].load_liquid(liquid=def_abc, volume=vol_abc / 8) + reagent_res.rows()[p][1].load_liquid(liquid=def_dtt, volume=vol_dtt / 8) + reagent_res.rows()[p][2].load_liquid(liquid=def_iaa, volume=vol_iaa / 8) + reagent_res.rows()[p][3].load_liquid(liquid=def_trypsin, volume=vol_trypsin / 8) + + def transfer(vol1, start, end, tip, mix_skip=0): + m1000.pick_up_tip(tip) + for i in range(num_col): + start_loc = start + end_loc = end[i] + m1000.aspirate(10, start_loc.top(z=0)) + m1000.aspirate(vol1, start_loc.bottom(z=0.2)) + ctx.delay(seconds=3) + m1000.air_gap(10) + m1000.dispense(10, end_loc.top(z=0)) + m1000.dispense(vol1 + 10, end_loc.top(z=0)) + m1000.blow_out() + m1000.return_tip() + if mix_skip == 0: + for i in range(num_col): + m1000.pick_up_tip(tip_loc_reused[i * 8]) + end_loc = end[i] + m1000.mix(5, 75, end_loc.bottom(z=0.2)) + m1000.blow_out(end_loc.top(z=0)) + m1000.touch_tip() + m1000.return_tip() + + # protocol + + tc.open_lid() + ctx.pause("Load Reagent Plate on Slot D3") + + ## add sample + if LABWARE_SAMPLE == 24: + num_rack_full = int(NUM_SAMPLES // 24) + num_tube_last_rack = int(NUM_SAMPLES % 24) + + slot = ["A2", "B2", "C2", "D2"] + + if num_rack_full == 0: + rack_loc = slot[0] + sample_rack = ctx.load_labware("opentrons_24_tuberack_nest_1.5ml_snapcap", rack_loc, "SAMPLES") + sample = sample_rack.wells()[:24] + vol_sample = SAMPLE_VOL + 20 + def_sample = ctx.define_liquid( + name="SAMPLES (Slot " + slot[0] + ")", description="Protein samples, volume per tube", display_color="#FF0000" + ) ## Red + + for i in range(NUM_SAMPLES): + start = sample[i] + end = sample_digested_in_well[i] + s1000.pick_up_tip(tip_loc_reused[i]) + s1000.aspirate(SAMPLE_VOL, start.bottom(z=0.2)) + ctx.delay(seconds=3) + s1000.air_gap(10) + s1000.dispense(10, end.top(z=0)) + s1000.dispense(SAMPLE_VOL, end.bottom(z=0.5)) + s1000.touch_tip() + s1000.return_tip() + + sample_rack.wells()[i].load_liquid(liquid=def_sample, volume=vol_sample / NUM_SAMPLES) + + else: + for n in range(num_rack_full): + sample_rack = ctx.load_labware("opentrons_24_tuberack_nest_1.5ml_snapcap", slot[n], "SAMPLES") + sample = sample_rack.wells()[:24] + vol_sample = SAMPLE_VOL + 20 + def_sample = ctx.define_liquid( + name="SAMPLES (Slot " + slot[n] + ")", description="Protein samples, volume per tube", display_color="#FF0000" + ) ## Red + + for i in range(24): + start = sample[i] + end = sample_digested_in_well[n * 24 + i] + s1000.pick_up_tip(tip_loc_reused[n * 24 + i]) + s1000.aspirate(SAMPLE_VOL, start.bottom(z=0.2)) + ctx.delay(seconds=3) + s1000.air_gap(10) + s1000.dispense(10, end.top(z=0)) + s1000.dispense(SAMPLE_VOL, end.bottom(z=0.5)) + s1000.touch_tip() + s1000.return_tip() + + sample_rack.wells()[i].load_liquid(liquid=def_sample, volume=vol_sample / 24) + + if num_tube_last_rack != 0: + last_rack_loc = slot[num_rack_full] + sample_rack = ctx.load_labware("opentrons_24_tuberack_nest_1.5ml_snapcap", last_rack_loc, "SAMPLES") + sample = sample_rack.wells()[:24] + vol_sample = SAMPLE_VOL + 20 + def_sample = ctx.define_liquid( + name="SAMPLES (Slot " + slot[num_rack_full] + ")", + description="Protein samples, volume per tube", + display_color="#FF0000", + ) ## Red + + for i in range(num_tube_last_rack): + start = sample[i] + end = sample_digested_in_well[num_rack_full * 24 + i] + s1000.pick_up_tip(tip_loc_reused[num_rack_full * 24 + i]) + s1000.aspirate(SAMPLE_VOL, start.bottom(z=0.2)) + ctx.delay(seconds=3) + s1000.air_gap(10) + s1000.dispense(10, end.top(z=0)) + s1000.dispense(SAMPLE_VOL, end.bottom(z=0.5)) + s1000.touch_tip() + s1000.return_tip() + + sample_rack.wells()[i].load_liquid(liquid=def_sample, volume=vol_sample / num_tube_last_rack) + + elif LABWARE_SAMPLE == 96: + sample_plate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A2", "SAMPLES") + sample = sample_plate.rows()[0][:num_col] + vol_sample = SAMPLE_VOL + 20 + def_sample = ctx.define_liquid(name="SAMPLES", description="Protein samples, volume per well", display_color="#FF0000") ## Red + for well in range(NUM_SAMPLES): + sample_plate.wells()[well].load_liquid(liquid=def_sample, volume=vol_sample / NUM_SAMPLES) + + for i in range(num_col): + start = sample[i] + end = sample_digested_in_col[i] + m1000.pick_up_tip(tip_loc_reused[i * 8]) + m1000.aspirate(SAMPLE_VOL, start.bottom(z=0.2)) + ctx.delay(seconds=3) + m1000.air_gap(10) + m1000.dispense(10, end.top(z=0)) + m1000.dispense(SAMPLE_VOL, end.bottom(z=0.5)) + m1000.touch_tip() + m1000.return_tip() + + ## add ABC + transfer(ABC_VOL, abc, sample_digested_in_col, tip_loc[0], 1) + + ## add DTT and incubate + transfer(DTT_VOL, dtt, sample_digested_in_col, tip_loc[8]) + + tc.set_lid_temperature(temperature=70) + tc.close_lid() + tc.set_block_temperature(temperature=DTT_TEMP, block_max_volume=100) + ctx.delay(minutes=DTT_TIME) + + ## add IAA and incubate + tc.set_block_temperature(temperature=IAA_TEMP, block_max_volume=100) + ctx.delay(minutes=5) + tc.open_lid() + transfer(IAA_VOL, iaa, sample_digested_in_col, tip_loc[16]) + + ctx.delay(minutes=IAA_TIME) + + ## add Trypsin and incubate + transfer(TRYPSIN_VOL, trypsin, sample_digested_in_col, tip_loc[24]) + tc.close_lid() + tc.set_block_temperature(temperature=TRYPSIN_TEMP, block_max_volume=100) + if DIGESTION_TIME_PRESET == 1: + ctx.delay(minutes=DIGESTION_TIME * 60) + else: + ctx.pause("Incubation for 1-24 hours") + tc.deactivate_block() + ctx.delay(minutes=10) + + tc.deactivate_lid() + tc.open_lid() + + ## Digestion Complete diff --git a/app-testing/files/protocols/pl_Takara_InFusionSnapAssembly_Flex.py b/app-testing/files/protocols/pl_Takara_InFusionSnapAssembly_Flex.py new file mode 100644 index 00000000000..d210996e638 --- /dev/null +++ b/app-testing/files/protocols/pl_Takara_InFusionSnapAssembly_Flex.py @@ -0,0 +1,271 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"VOL_REACTION":10,"NUM_COMBO":8,"insert_1":2,"vector_1":1,"insert_2":2,"vector_2":1,"insert_3":2,"vector_3":1,"insert_4":2,"vector_4":1,"insert_5":2,"vector_5":1,"insert_6":2,"vector_6":1,"insert_7":2,"vector_7":1,"insert_8":2,"vector_8":1,"protocol_filename":"Takara_InFusionSnapAssembly_Flex"}""" + ) + return [_all_values[n] for n in names] + + +metadata = {"protocolName": "Takara In-Fusion Snap Assembly Kit on Flex", "author": "Boren Lin, Opentrons", "source": ""} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + + +def run(ctx): + + VOL_REACTION = 10 + ## can be 5 or 10 uL + NUM_COMBO = 8 + ## maxi. 8 + + try: + [VOL_REACTION, NUM_COMBO] = get_values("VOL_REACTION", "NUM_COMBO") + except NameError: + # get_values is not defined, so proceed with defaults + pass + + insert_1 = 2 + vector_1 = 1 + insert_2 = 2 + vector_2 = 1 + insert_3 = 2 + vector_3 = 1 + insert_4 = 2 + vector_4 = 1 + insert_5 = 2 + vector_5 = 1 + insert_6 = 2 + vector_6 = 1 + insert_7 = 2 + vector_7 = 1 + insert_8 = 2 + vector_8 = 1 + + try: + [ + insert_1, + vector_1, + insert_2, + vector_2, + insert_3, + vector_3, + insert_4, + vector_4, + insert_5, + vector_5, + insert_6, + vector_6, + insert_7, + vector_7, + insert_8, + vector_8, + ] = get_values( + "insert_1", + "vector_1", + "insert_2", + "vector_2", + "insert_3", + "vector_3", + "insert_4", + "vector_4", + "insert_5", + "vector_5", + "insert_6", + "vector_6", + "insert_7", + "vector_7", + "insert_8", + "vector_8", + ) + except NameError: + # get_values is not defined, so proceed with defaults + pass + + VOL_INSERT = [] + VOL_VECTOR = [] + for i in range(1, NUM_COMBO + 1): + if int(locals()["insert_" + str(i)]) <= 0: + raise Exception("Invalid liqud volume") + else: + VOL_INSERT.append(int(locals()["insert_" + str(i)])) + VOL_VECTOR.append(int(locals()["vector_" + str(i)])) + # at least one insert must be set + if len(VOL_INSERT) == 0: + raise Exception("No inserts") + + if VOL_REACTION == 10: + VOL_MASTERMIX = 2 + num_row = len(VOL_INSERT) + for i in range(num_row): + vol_water = VOL_REACTION - VOL_MASTERMIX - VOL_INSERT[i] - VOL_VECTOR[i] + if vol_water < 0: + raise Exception("Invalid liqud volume") + + elif VOL_REACTION == 5: + VOL_MASTERMIX = 1 + num_row = len(VOL_INSERT) + for i in range(num_row): + vol_water = VOL_REACTION - VOL_MASTERMIX - VOL_INSERT[i] - VOL_VECTOR[i] + if vol_water < 0: + raise Exception("Invalid liqud volume") + + else: + raise Exception("Invalid liqud volume") + + # load labware and pipette + temp = ctx.load_module("Temperature Module Gen2", "D3") + temp_adapter = temp.load_adapter("opentrons_96_well_aluminum_block") + working_plate = temp_adapter.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "wokring plate") + + tips = [ctx.load_labware("opentrons_flex_96_filtertiprack_50ul", slot, "50uL Tips") for slot in ["C2", "B2"]] + m50 = ctx.load_instrument("flex_8channel_50", "left", tip_racks=tips) + s50 = ctx.load_instrument("flex_1channel_50", "right", tip_racks=tips) + + reagent_stock = ctx.load_labware("opentrons_24_tuberack_nest_1.5ml_snapcap", "D2", "Inserts, Vectors, Master Mix, Water") + + ctx.load_trash_bin("A3") + + # liquid + for i in range(len(VOL_INSERT)): + insert_vol_stock = VOL_INSERT[i] * 6 + 10 + insert_def = ctx.define_liquid( + name="Insert# " + str(i + 1), description="DNA Fragments to be Inserted", display_color="#704848" + ) ## Brown + reagent_stock.wells()[i].load_liquid(liquid=insert_def, volume=insert_vol_stock) + + vector_vol_stock = VOL_VECTOR[i] * 6 + 10 + vector_def = ctx.define_liquid(name="Vector# " + str(i + 1), description="Linearized Vectors", display_color="#0EFF00") ## Green + reagent_stock.wells()[i + 8].load_liquid(liquid=vector_def, volume=vector_vol_stock) + + mastermix_vol_stock = VOL_MASTERMIX * len(VOL_INSERT) * 6 + 10 + mastermix_def = ctx.define_liquid(name="Mastermix", description="In-Fusion Snap Assembly Master Mix", display_color="#FF0000") ## Red + reagent_stock.wells()[16].load_liquid(liquid=mastermix_def, volume=mastermix_vol_stock) + + water_def = ctx.define_liquid(name="Water", description="Molecular biology Grade Water ", display_color="#00FFF2") ## Light Blue + reagent_stock.wells()[20].load_liquid(liquid=water_def, volume=500) + + inserts = reagent_stock.wells()[:8] + vectors = reagent_stock.wells()[8:16] + mastermix = reagent_stock.wells()[16] + water = reagent_stock.wells()[20] + pre_mix = working_plate.wells()[:8] + reaction = working_plate.rows()[0][:6] + + # perform + ## transfer water + s50.pick_up_tip() + for i in range(len(VOL_INSERT)): + vol_water = VOL_REACTION - VOL_MASTERMIX - VOL_INSERT[i] - VOL_VECTOR[i] + if vol_water != 0: + vol = vol_water * 6 + start = water + end = pre_mix[i] + if vol > 50: + vol = vol / 2 + for _ in range(2): + s50.mix(1, vol, start) + s50.aspirate(vol, start) + s50.dispense(vol, end) + elif vol < 50: + s50.mix(1, vol, start) + s50.aspirate(vol, start) + s50.dispense(vol, end) + s50.drop_tip() + + ## transfer master mix + vol = VOL_MASTERMIX * 6 + s50.pick_up_tip() + for i in range(len(VOL_INSERT)): + start = mastermix + end = pre_mix[i] + if vol > 50: + vol = vol / 2 + for _ in range(2): + s50.mix(3, vol, start) + ctx.delay(seconds=5) + s50.aspirate(vol, start) + ctx.delay(seconds=5) + s50.dispense(vol, end) + ctx.delay(seconds=5) + elif vol < 50 and vol > 0: + s50.mix(3, vol, start) + ctx.delay(seconds=5) + s50.aspirate(vol, start) + ctx.delay(seconds=5) + s50.dispense(vol, end) + ctx.delay(seconds=5) + s50.drop_tip() + + ## transfer vector + for i in range(len(VOL_INSERT)): + if VOL_INSERT[i] > 0: + vol = VOL_VECTOR[i] * 6 + s50.pick_up_tip() + start = vectors[i] + end = pre_mix[i] + if vol > 50: + vol = vol / 2 + for _ in range(2): + s50.mix(3, vol, start) + ctx.delay(seconds=2) + s50.aspirate(vol, start) + ctx.delay(seconds=2) + s50.dispense(vol, end) + ctx.delay(seconds=2) + elif vol < 50: + s50.mix(3, vol, start) + ctx.delay(seconds=2) + s50.aspirate(vol, start) + ctx.delay(seconds=2) + s50.dispense(vol, end) + ctx.delay(seconds=2) + s50.drop_tip() + + ## transfer insert + for i in range(len(VOL_INSERT)): + vol = VOL_INSERT[i] * 6 + s50.pick_up_tip() + start = inserts[i] + end = pre_mix[i] + if vol > 50: + vol = vol / 2 + for _ in range(2): + s50.mix(3, vol, start) + ctx.delay(seconds=2) + s50.aspirate(vol, start) + ctx.delay(seconds=2) + s50.dispense(vol, end) + ctx.delay(seconds=2) + elif vol < 50: + s50.mix(3, vol, start) + ctx.delay(seconds=2) + s50.aspirate(vol, start) + ctx.delay(seconds=2) + s50.dispense(vol, end) + ctx.delay(seconds=2) + s50.drop_tip() + + ## prepare reactions + m50.pick_up_tip() + start = reaction[0] + m50.mix(3, VOL_REACTION * 5, start) + ctx.delay(seconds=2) + m50.aspirate(VOL_REACTION * 5, start) + ctx.delay(seconds=2) + for j in range(5): + end = reaction[j + 1] + m50.dispense(VOL_REACTION, end) + ctx.delay(seconds=2) + m50.drop_tip() + + ## incubation + ctx.pause("Seal the plate") + temp.set_temperature(50) + ctx.delay(minutes=15) + temp.set_temperature(4) + ctx.pause("Reaction complete") + temp.deactivate() diff --git a/app-testing/files/protocols/pl_Zymo_Magbead_DNA_Cells_Flex_96_channel.py b/app-testing/files/protocols/pl_Zymo_Magbead_DNA_Cells_Flex_96_channel.py new file mode 100644 index 00000000000..4298ba7e576 --- /dev/null +++ b/app-testing/files/protocols/pl_Zymo_Magbead_DNA_Cells_Flex_96_channel.py @@ -0,0 +1,510 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"dry_run":false,"temp_mod":true,"heater_shaker":true,"tip_mixing":false,"wash1_vol":500,"wash2_vol":900,"wash3_vol":900,"wash4_vol":900,"lysis_vol":200,"bind_vol":600,"bind2_vol":500,"elution_vol":75,"protocol_filename":"Zymo_Magbead_DNA_Cells-Flex_96_channel"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +import json +import math +from opentrons import types +import numpy as np + +metadata = {"author": "Zach Galluzzo "} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + trash_chute = False # If false, waste bin loaded in D3, if True, trash chute loaded there + USE_GRIPPER = True + dry_run = False + temp_mod = True + heater_shaker = True + tip_mixing = False # this will only be activated if heater_shaker is False + + wash1_vol = 500 + wash2_vol = 900 + wash3_vol = 900 + wash4_vol = 900 + lysis_vol = 200 + bind_vol = 600 + bind2_vol = 500 + elution_vol = 75 + + try: + [ + trash_chute, + USE_GRIPPER, + dry_run, + temp_mod, + heater_shaker, + tip_mixing, + wash1_vol, + wash2_vol, + wash3_vol, + bind_vol, + lysis_vol, + elution_vol, + ] = get_values( # noqa: F821 + "trash_chute", + "USE_GRIPPER", + "dry_run", + "temp_mod", + "heater_shaker", + "tip_mixing", + "wash1_vol", + "wash2_vol", + "wash3_vol", + "bind_vol", + "lysis_vol", + "elution_vol", + ) + + except NameError: + pass + + # Just to be safe + if heater_shaker: + tip_mixing = False + + deepwell_type = "nest_96_wellplate_2ml_deep" + if not dry_run: + settling_time = 2 + lysis_incubation = 30 + if dry_run: + settling_time = 0.25 + lysis_incubation = 0.25 + sample_vol = 10 + bead_vol = 25 + PK_vol = 20 + lysis_total_vol = PK_vol + lysis_vol + starting_vol = lysis_vol + sample_vol + binding_buffer_vol = bead_vol + bind_vol + + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Sample Plate") + + samples_m = sample_plate.wells()[0] + + if temp_mod: + temp = ctx.load_module("temperature module gen2", "A3") + tempblock = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = tempblock.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate") + + magblock = ctx.load_module("magneticBlockV1", "C1") + + #'#008000','#A52A2A','#00FFFF','#0000FF','#800080','#ADD8E6','#FF0000','#FFFF00','#FF00FF','#00008B','#7FFFD4','#FFC0CB','#FFA500','#00FF00','#C0C0C0' + # Defining Liquids + + lysis_reservoir = ctx.load_labware(deepwell_type, "D2", "Lysis ") + lysis_res = lysis_reservoir.wells()[0] + lysis_buffer = ctx.define_liquid(name="Lysis Buffer", description="Lysis Buffer", display_color="#008000") + for well in lysis_reservoir.wells(): + well.load_liquid(liquid=lysis_buffer, volume=lysis_vol + 91) + pk_buffer = ctx.define_liquid(name="PK Buffer", description="Proteinase K", display_color="#008000") + for well in lysis_reservoir.wells(): + well.load_liquid(liquid=pk_buffer, volume=PK_vol + 9) + + bind_reservoir = ctx.load_labware(deepwell_type, "C2", "Beads and binding ") + bind_res = bind_reservoir.wells()[0] + bind_buffer = ctx.define_liquid(name="Binding Buffer", description="Binding Buffer", display_color="#A52A2A") + for well in bind_reservoir.wells(): + well.load_liquid(liquid=bind_buffer, volume=bind_vol + 96) + bead_buffer = ctx.define_liquid(name="Magbeads", description="Magnetic Beads", display_color="#A52A2A") + for well in bind_reservoir.wells(): + well.load_liquid(liquid=bead_buffer, volume=bead_vol + 4) + + bind2_reservoir = ctx.load_labware(deepwell_type, "C3", "Binding 2 ") + bind2_res = bind2_reservoir.wells()[0] + bind2_buffer = ctx.define_liquid(name="Bind Buffer", description="Second Bind Buffer (No beads)", display_color="#00FFFF") + for well in bind2_reservoir.wells(): + well.load_liquid(liquid=bind2_buffer, volume=bind2_vol + 100) + + wash1_reservoir = ctx.load_labware(deepwell_type, "B1", "Wash 1 ") + wash1_res = wash1_reservoir.wells()[0] + wash1_buffer = ctx.define_liquid(name="Wash 1", description="Wash 1 Buffer", display_color="#0000FF") + for well in wash1_reservoir.wells(): + well.load_liquid(liquid=wash1_buffer, volume=wash1_vol + 100) + + wash2_reservoir = ctx.load_labware(deepwell_type, "B2", "Wash 2 ") + wash2_res = wash2_reservoir.wells()[0] + wash2_buffer = ctx.define_liquid(name="Wash 2", description="Wash 2 Buffer", display_color="#800080") + for well in wash2_reservoir.wells(): + well.load_liquid(liquid=wash2_buffer, volume=(2 * wash2_vol) + 100) + + wash4_reservoir = ctx.load_labware(deepwell_type, "B3", "Wash 3 ") + wash4_res = wash4_reservoir.wells()[0] + wash4_buffer = ctx.define_liquid(name="Wash 4", description="Wash 4 Buffer", display_color="#ADD8E6") + for well in wash4_reservoir.wells(): + well.load_liquid(liquid=wash4_buffer, volume=wash4_vol + 100) + + elution_res = elutionplate.wells()[0] + elution_buffer = ctx.define_liquid(name="Elution Buffer", description="Elution Buffer", display_color="#FF0000") + for well in elutionplate.wells(): + well.load_liquid(liquid=elution_buffer, volume=elution_vol + 5) + + # Load tips + tips = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + tips1 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + + # load 96 channel pipette + pip = ctx.load_instrument("flex_96channel_1000", mount="left") + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + pip.flow_rate.blow_out = 300 + + def resuspend_pellet(vol, plate, reps=3): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=1, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0.75, y=0.75, z=1)) + loc3 = plate.bottom().move(types.Point(x=0, y=1, z=1)) + loc4 = plate.bottom().move(types.Point(x=-0.75, y=0.75, z=1)) + loc5 = plate.bottom().move(types.Point(x=-1, y=0, z=1)) + loc6 = plate.bottom().move(types.Point(x=-0.75, y=0 - 0.75, z=1)) + loc7 = plate.bottom().move(types.Point(x=0, y=-1, z=1)) + loc8 = plate.bottom().move(types.Point(x=0.75, y=-0.75, z=1)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc2) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc3) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc4) + pip.dispense(mixvol, loc4) + pip.aspirate(mixvol, loc5) + pip.dispense(mixvol, loc5) + pip.aspirate(mixvol, loc6) + pip.dispense(mixvol, loc6) + pip.aspirate(mixvol, loc7) + pip.dispense(mixvol, loc7) + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + def bead_mix(vol, plate, reps=5): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=0, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0, y=0, z=8)) + loc3 = plate.bottom().move(types.Point(x=0, y=0, z=16)) + loc4 = plate.bottom().move(types.Point(x=0, y=0, z=24)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc4) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + # Just in case + if heater_shaker: + h_s.close_labware_latch() + + # Start Protocol + + # Mix and Transfer Lysis + ctx.comment("------Mix Shield and PK in , Transfer to Sample Plate, Mix in Sample Plate-----") + pip.pick_up_tip(tips) + pip.aspirate(lysis_total_vol, lysis_res) + pip.air_gap(20) + pip.dispense(pip.current_volume, samples_m) + for _ in range(8 if not dry_run else 1): + pip.aspirate(lysis_total_vol * 0.9, samples_m) + pip.dispense(pip.current_volume, samples_m.bottom(20)) + if not tip_mixing: + pip.return_tip() + + # Mix in sample plate + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=lysis_incubation, msg="Please wait " + str(lysis_incubation) + " minutes to allow for proper lysis mixing.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + for x in range(4): + bead_mix(lysis_total_vol, samples_m, reps=8) + ctx.delay(minutes=2) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 30 minutes at 2000 rpm.") + + # Transfer and mix bind&beads + ctx.comment("------Mixing Beads with Binding, then Transfer to Sample Plate-----") + pip.pick_up_tip(tips) + bead_mix(binding_buffer_vol, bind_res, reps=5 if not dry_run else 1) + pip.aspirate(binding_buffer_vol, bind_res) + pip.dispense(binding_buffer_vol, samples_m) + if binding_buffer_vol + starting_vol < 1000: + mix_vol = binding_buffer_vol + starting_vol + else: + mix_vol = 1000 + bead_mix(mix_vol, samples_m, reps=7 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + # Shake for binding incubation + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Please allow 10 minutes for the beads to bind the DNA.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + for x in range(2): + bead_mix(binding_buffer_vol, samples_m, reps=6) + ctx.delay(minutes=2) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 10 minutes at 1800 rpm.") + + ctx.comment("------Moving Plate to Magnet to Begin Pelleting-----") + if heater_shaker: + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + # Remove Supernatant and move off magnet + ctx.comment("------Removing Supernatant-----") + pip.pick_up_tip(tips) + re_vol = binding_buffer_vol + starting_vol + pip.aspirate(re_vol, samples_m.bottom(0.3)) + pip.dispense(re_vol, bind_res) + if re_vol > 1000: + dif = (starting_vol + binding_buffer_vol) - 1000 + pip.aspirate(dif + 50, samples_m.bottom(0.1)) + pip.dispense(dif + 50, bind_res) + pip.return_tip() + + # Transfer plate from magnet to H/S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # Quick Bind #2 + ctx.comment("-----Beginning Bind #2-----") + pip.pick_up_tip(tips) + pip.aspirate(bind2_vol, bind2_res) + pip.air_gap(20) + pip.dispense(pip.current_volume, samples_m.top()) + resuspend_pellet(bind2_vol, samples_m, reps=4 if not dry_run else 1) + pip.blow_out() + if not tip_mixing: + pip.return_tip() + + ctx.comment("-----Mixing Bind2 with Beads-----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=1 if not dry_run else 0.25, msg="Shake at 2000 rpm for 1 minutes.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(bind2_vol, samples_m, reps=5) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 1 minute.") + + ctx.comment("------Moving Plate to Magnet to Begin Pelleting-----") + if heater_shaker: + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + # Remove Supernatant and move off magnet + ctx.comment("------Removing Supernatant-----") + pip.pick_up_tip(tips) + pip.aspirate(bind2_vol, samples_m.bottom(0.3)) + pip.dispense(bind2_vol, bind_res) + pip.return_tip() + + # Transfer plate from magnet to H/S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + # Washes + for i in range(4 if not dry_run else 1): + if i == 0: + wash_res = wash1_res + wash_vol = wash1_vol + waste_res = bind_res + whichwash = 1 + height = 1 + if i == 1: + wash_res = wash2_res + wash_vol = wash2_vol + waste_res = bind2_res + whichwash = 2 + height = 15 + if i == 2: + wash_res = wash2_res + wash_vol = wash3_vol + waste_res = bind2_res + whichwash = 3 + height = 1 + if i == 3: + wash_res = wash4_res + wash_vol = wash4_vol + waste_res = wash2_res + whichwash = 4 + height = 1 + + ctx.comment("------Beginning Wash #" + str(whichwash) + "-----") + pip.pick_up_tip(tips) + pip.aspirate(wash_vol, wash_res.bottom(height)) + pip.dispense(wash_vol, samples_m) + pip.air_gap(20) + if not tip_mixing: + pip.return_tip() + pip.home() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=5 if not dry_run else 0.25) + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(wash_vol, samples_m, reps=12) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 1800 rpm.") + + if heater_shaker: + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay( + minutes=settling_time, + msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet in wash #" + str(whichwash) + ".", + ) + + # Remove Supernatant and move off magnet + ctx.comment("------Removing Supernatant-----") + pip.pick_up_tip(tips) + pip.aspirate(wash_vol, samples_m.bottom(0.3)) + pip.dispense(wash_vol, waste_res.top()) + + pip.return_tip() + # Transfer plate from magnet to H/S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.comment("------Drying Beads for 10 minutes-----") + + if heater_shaker and not dry_run: + h_s.set_and_wait_for_temperature(55) + + drybeads = 9 if not dry_run else 0.5 # Number of minutes you want to dry for + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + + # Elution + ctx.comment("------Beginning Elution-----") + pip.pick_up_tip(tips1) + pip.flow_rate.aspirate = 25 + pip.aspirate(elution_vol, elution_res) + pip.dispense(elution_vol, samples_m) + if not tip_mixing: + pip.return_tip() + pip.home() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please wait 5 minutes to allow dna to elute from beads.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(elution_vol, samples_m, reps=12) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place plate on shaker at 2000 rpm for 5 minutes.") + + # Transfer plate to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + ctx.delay(minutes=settling_time, msg="Please wait " + str(settling_time) + " minute(s) for beads to pellet.") + + ctx.comment("------Transfer DNA to Final Elution Plate-----") + pip.pick_up_tip(tips1) + pip.aspirate(elution_vol, samples_m.bottom(0.15)) + pip.dispense(elution_vol, elutionplate.wells()[0]) + pip.return_tip() + + pip.home() diff --git a/app-testing/files/protocols/pl_Zymo_Magbead_DNA_Cells_Flex_multi.py b/app-testing/files/protocols/pl_Zymo_Magbead_DNA_Cells_Flex_multi.py new file mode 100644 index 00000000000..4381c3996c2 --- /dev/null +++ b/app-testing/files/protocols/pl_Zymo_Magbead_DNA_Cells_Flex_multi.py @@ -0,0 +1,649 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"dry_run":false,"mount":"left","temp_mod":true,"res_type":"nest_12_reservoir_15ml","heater_shaker":true,"num_samples":8,"wash1_vol":500,"wash2_vol":900,"wash3_vol":900,"lysis_vol":200,"sample_vol":10,"bind_vol":600,"bind2_vol":500,"elution_vol":75,"protocol_filename":"Zymo_Magbead_DNA_Cells-Flex_multi"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +import json +import math +from opentrons import types +import numpy as np + +metadata = { + "author": "Zach Galluzzo ", +} + +requirements = {"robotType": "Flex", "apiLevel": "2.16"} + +""" +Here is where you can modify the magnetic module engage height: +""" +whichwash = 1 +sample_max = 48 +tip1k = 0 +tip200 = 0 +drop_count = 0 + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + trash_chute = False # If this is true, trash chute is loaded in D3, otherwise trash bin is loaded there + USE_GRIPPER = True + dry_run = False + mount = "left" + res_type = "nest_12_reservoir_15ml" + temp_mod = True + heater_shaker = True + + num_samples = 2 + wash1_vol = 500 + wash2_vol = 900 + wash3_vol = 900 + lysis_vol = 200 + sample_vol = 10 # Sample should be pelleted tissue/bacteria/cells + bind_vol = 600 + bind2_vol = 500 + elution_vol = 75 + + try: + [ + res_type, + temp_mod, + trash_chute, + USE_GRIPPER, + dry_run, + mount, + num_samples, + heater_shaker, + wash1_vol, + wash2_vol, + wash3_vol, + lysis_vol, + sample_vol, + bind_vol, + bind2_vol, + elution_vol, + ] = get_values( # noqa: F821 + "res_type", + "temp_mod", + "trash_chute", + "USE_GRIPPER", + "dry_run", + "mount", + "num_samples", + "heater_shaker", + "wash1_vol", + "wash2_vol", + "wash3_vol", + "lysis_vol", + "sample_vol", + "bind_vol", + "bind2_vol", + "elution_vol", + ) + + except NameError: + pass + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + + if not dry_run: + settling_time = 2 + lysis_incubation = 30 + else: + settling_time = 0.25 + lysis_incubation = 0.25 + PK_vol = 20 + bead_vol = 25 + lysis_total_vol = lysis_vol + PK_vol + starting_vol = lysis_vol + sample_vol + binding_buffer_vol = bind_vol + bead_vol + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Samples") + h_s.close_labware_latch() + + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Samples") + + if temp_mod: + temp = ctx.load_module("temperature module gen2", "A3") + temp_block = temp.load_adapter("opentrons_96_well_aluminum_block") + elutionplate = temp_block.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate") + + magblock = ctx.load_module("magneticBlockV1", "C1") + waste = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste").wells()[0].top() + res1 = ctx.load_labware(res_type, "D2", "reagent reservoir 1") + res2 = ctx.load_labware(res_type, "C2", "reagent reservoir 2") + num_cols = math.ceil(num_samples / 8) + + # Load tips and combine all similar boxes + tips1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "Tips 1") + tips1001 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "Tips 2") + tips1002 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B1", "Tips 3") + tips = [*tips1000.wells()[8 * (num_cols) : 96], *tips1001.wells(), *tips1002.wells()] + tips_sn = tips1000.wells()[: 8 * (num_cols)] + # load instruments + m1000 = ctx.load_instrument("flex_8channel_1000", mount) + + """ + Here is where you can define the locations of your reagents. + """ + lysis_ = res1.wells()[0] + binding_buffer = res1.wells()[1:4] + bind2_res = res1.wells()[4:6] + wash1 = res1.wells()[6:8] + elution_solution = res1.wells()[-1] + wash2 = res2.wells()[:6] + wash3 = res2.wells()[6:] + + samples_m = sample_plate.rows()[0][:num_cols] + elution_samples_m = elutionplate.rows()[0][:num_cols] + # Redefine per well for liquid definitions + samps = sample_plate.wells()[: (8 * num_cols)] + elution_samps = elutionplate.wells()[: (8 * num_cols)] + + colors = [ + "#008000", + "#008000", + "#A52A2A", + "#A52A2A", + "#00FFFF", + "#0000FF", + "#800080", + "#ADD8E6", + "#FF0000", + "#FFFF00", + "#FF00FF", + "#00008B", + "#7FFFD4", + "#FFC0CB", + "#FFA500", + "#00FF00", + "#C0C0C0", + ] + + locations = [lysis_, lysis_, binding_buffer, binding_buffer, bind2_res, wash1, wash2, wash3, elution_solution] + vols = [lysis_vol, PK_vol, bead_vol, bind_vol, bind2_vol, wash1_vol, wash2_vol, wash3_vol, elution_vol] + liquids = ["Lysis", "PK", "Beads", "Binding", "Binding 2", "Wash 1", "Wash 2", "Wash 3", "Final Elution"] + + # Defining liquids per sample well + samples = ctx.define_liquid(name="Samples", description="Samples", display_color="#C0C0C0") + + for i in samps: + i.load_liquid(liquid=samples, volume=0) + + delete = len(colors) - len(liquids) + + if delete >= 1: + for i in range(delete): + colors.pop(-1) + + # Defining liquids per reservoir well + def liquids_(liq, location, color, vol): + sampnum = 8 * (math.ceil(num_samples / 8)) + """ + Takes an individual liquid at a time and adds the color to the well + in the description. + """ + # Volume Calculation + if liq == "PK": + extra_samples = math.ceil(1500 / lysis_vol) + + elif liq == "Beads": + extra_samples = math.ceil(1500 / bind_vol) + + else: + extra_samples = math.ceil(1500 / vol) + + # Defining and assigning liquids to wells + if isinstance(location, list): + limit = sample_max / len(location) # Calculates samples/ res well + iterations = math.ceil(sampnum / limit) + left = sampnum - limit + while left > limit: + left = left - limit + if left > 0: + last_iteration_samp_num = left + elif left < 0: + last_iteration_samp_num = sampnum + else: + last_iteration_samp_num = limit + + samples_per_well = [] + + for i in range(iterations): + # append the left over on the last iteration + if i == (iterations - 1): + samples_per_well.append(last_iteration_samp_num) + else: + samples_per_well.append(limit) + + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + for sample, well in zip(samples_per_well, location[: len(samples_per_well)]): + v = vol * (sample + extra_samples) + well.load_liquid(liquid=liq, volume=v) + else: + v = vol * (sampnum + extra_samples) + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + location.load_liquid(liquid=liq, volume=v) + + for x, (ll, l, c, v) in enumerate(zip(liquids, locations, colors, vols)): + liquids_(ll, l, c, v) + + m1000.flow_rate.aspirate = 300 + m1000.flow_rate.dispense = 300 + m1000.flow_rate.blow_out = 300 + + def tiptrack(pip, tipbox): + global tip1k + global drop_count + if tipbox == tips: + m1000.pick_up_tip(tipbox[int(tip1k)]) + tip1k = tip1k + 8 + + drop_count = drop_count + 8 + if drop_count >= 150: + drop_count = 0 + ctx.pause("Please empty the waste bin of all the tips before continuing.") + + def blink(): + for i in range(3): + ctx.set_rail_lights(True) + ctx.delay(minutes=0.01666667) + ctx.set_rail_lights(False) + ctx.delay(minutes=0.01666667) + + def remove_supernatant(vol): + ctx.comment("-----Removing Supernatant-----") + m1000.flow_rate.aspirate = 30 + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + + def _waste_track(vol): + global waste_vol + waste_vol = waste_vol + (vol * 8) + if waste_vol >= 185000: + m1000.home() + blink() + ctx.pause("Please empty liquid waste before resuming.") + waste_vol = 0 + + for i, m in enumerate(samples_m): + m1000.pick_up_tip(tips_sn[8 * i]) + loc = m.bottom(0.5) + for _ in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, m.top()) + m1000.move_to(m.center()) + m1000.transfer(vol_per_trans, loc, waste, new_tip="never", air_gap=20) + m1000.blow_out(waste) + m1000.air_gap(20) + m1000.drop_tip(tips_sn[8 * i]) + m1000.flow_rate.aspirate = 300 + + # Transfer from Magdeck plate to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def bead_mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top().move(types.Point(x=0, y=0, z=5)) + aspbot = well.bottom().move(types.Point(x=0, y=2, z=1)) + asptop = well.bottom().move(types.Point(x=0, y=-2, z=2)) + disbot = well.bottom().move(types.Point(x=0, y=2, z=3)) + distop = well.top().move(types.Point(x=0, y=1, z=-5)) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, aspbot) + pip.dispense(vol, distop) + pip.aspirate(vol, asptop) + pip.dispense(vol, disbot) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, aspbot) + pip.dispense(vol, aspbot) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(1) + disp = well.top(-8) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def lysis(vol, source): + ctx.comment("-----Beginning Lysis Steps-----") + num_transfers = math.ceil(vol / 980) + tiptrack(m1000, tips) + for i in range(num_cols): + src = source + tvol = vol / num_transfers + # Mix Shield and PK before transferring first time + if i == 0: + for x in range(3 if not dry_run else 1): + m1000.aspirate(vol, src.bottom(1)) + m1000.dispense(vol, src.bottom(8)) + # Transfer Shield and PK + for t in range(num_transfers): + m1000.aspirate(tvol, src.bottom(1)) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, samples_m[i].top()) + + # Mix shield and pk with samples + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + mixing(samples_m[i], m1000, tvol, reps=5 if not dry_run else 1) + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=lysis_incubation if not dry_run else 0.25, msg="Shake at 1800 rpm for 30 minutes.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on a shaker at 1800 rpm for 30 minutes") + + def bind(vol1, vol2): + """ + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead bining. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + ctx.comment("-----Beginning Binding Steps-----") + for i, well in enumerate(samples_m): + tiptrack(m1000, tips) + num_trans = math.ceil(vol1 / 980) + vol_per_trans = vol1 / num_trans + source = binding_buffer[i // 2] + if i == 0: + reps = 5 + else: + reps = 2 + bead_mixing(source, m1000, vol_per_trans, reps=reps if not dry_run else 1) + # Transfer beads and binding from source to H-S plate + for t in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, source.top()) + m1000.transfer(vol_per_trans, source, well.top(), air_gap=20, new_tip="never") + m1000.air_gap(20) + bead_mixing(well, m1000, vol_per_trans, reps=8 if not dry_run else 1) + m1000.blow_out() + m1000.air_gap(10) + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1800) + ctx.delay(minutes=10 if not dry_run else 0.25, msg="Shake at 1800 rpm for 10 minutes.") + h_s.deactivate_shaker() + + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 1800 rpm for 10 minutes") + + # Transfer from H-S plate to Magdeck plate + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for bindi in np.arange(settling_time + 1, 0, -0.5): # Settling time delay with countdown timer + ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") + + # remove initial supernatant + remove_supernatant(vol1 + starting_vol) + + ctx.comment("-----Beginning Bind #2 Steps-----") + tiptrack(m1000, tips) + for i, well in enumerate(samples_m): + num_trans = math.ceil(vol2 / 980) + vol_per_trans = vol2 / num_trans + source = bind2_res[i // 3] + if i == 0 and num_cols >= 3: + height = 10 + elif i == 3 and num_cols == 6: + height = 10 + else: + height = 1 + # Transfer beads and binding from source to H-S plate + for t in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, source.top()) + m1000.transfer(vol_per_trans, source.bottom(height), well.top(), air_gap=20, new_tip="never") + m1000.air_gap(20) + + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + bead_mixing(samples_m[i], m1000, vol_per_trans, reps=3 if not dry_run else 1) + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=1 if not dry_run else 0.25, msg="Shake at 2000 rpm for 1 minutes.") + h_s.deactivate_shaker() + + else: + if not dry_run: + ctx.pause("Place on shaker at 2000 rpm for 1 minute.") + + # Transfer from H-S plate to Magdeck plate + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for bindi in np.arange(settling_time + 1, 0, -0.5): # Settling time delay with countdown timer + ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") + + # remove initial supernatant + remove_supernatant(vol2 + 25) + + def wash(vol, source): + + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + whichwash = 1 + const = 6 // len(source) + if source == wash2: + whichwash = 2 + const = 6 // len(source) + height = 1 + if source == wash3: + whichwash = 3 + const = 6 // len(source) + height = 1 + + ctx.comment("-----Wash #" + str(whichwash) + " is starting now------") + + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + + tiptrack(m1000, tips) + for i, m in enumerate(samples_m): + if source == wash1: + if i == 0 and num_cols >= 3: + height = 10 + elif i == 3 and num_cols == 6: + height = 10 + else: + height = 1 + src = source[i // const] + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.transfer(vol_per_trans, src.bottom(height), m.top(), air_gap=20, new_tip="never") + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(1800) + ctx.delay(minutes=5 if not dry_run else 0.25) + h_s.deactivate_shaker() + + else: + if not dry_run: + ctx.pause(msg="Shaker for 5 minutes at 1800 rpm") + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for washi in np.arange(settling_time, 0, -0.5): # settling time timer for washes + ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in wash " + str(whichwash) + " incubation.") + + remove_supernatant(vol) + + def elute(vol): + tiptrack(m1000, tips) + for i, m in enumerate(samples_m): + m1000.aspirate(vol, elution_solution) + m1000.air_gap(20) + m1000.dispense(m1000.current_volume, m.top(-3)) + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Shake on H-S for 5 minutes at 2000 rpm.") + h_s.deactivate_shaker() + + else: + if not dry_run: + ctx.pause(msg="Place on a shaker at 2000 rpm for 5 minutes.") + + # Transfer back to magnet + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for elutei in np.arange(settling_time, 0, -0.5): + ctx.delay(minutes=0.5, msg="Incubating on MagDeck for " + str(elutei) + " more minutes.") + + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + tiptrack(m1000, tips) + m1000.flow_rate.dispense = 100 + m1000.flow_rate.aspirate = 25 + m1000.transfer(vol, m.bottom(0.15), e.bottom(5), air_gap=20, new_tip="never") + m1000.blow_out(e.top(-2)) + m1000.air_gap(20) + m1000.drop_tip() + + m1000.flow_rate.aspirate = 150 + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + lysis(lysis_vol, lysis_) + bind(binding_buffer_vol, bind2_vol) + wash(wash1_vol, wash1) + if not dry_run: + wash(wash2_vol, wash2) + wash(wash3_vol, wash3) + drybeads = 9 # Number of minutes you want to dry for + if heater_shaker: + h_s.set_and_wait_for_temperature(55) + else: + drybeads = 0.5 + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + elute(elution_vol) + if heater_shaker: + h_s.deactivate_heater() diff --git a/app-testing/files/protocols/pl_Zymo_Quick_RNA_Cells_Flex_96_Channel.py b/app-testing/files/protocols/pl_Zymo_Quick_RNA_Cells_Flex_96_Channel.py new file mode 100644 index 00000000000..578869c76c5 --- /dev/null +++ b/app-testing/files/protocols/pl_Zymo_Quick_RNA_Cells_Flex_96_Channel.py @@ -0,0 +1,558 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"dry_run":false,"temp_mod":true,"heater_shaker":true,"tip_mixing":false,"sample_vol":300,"wash_vol":400,"stop_vol":500,"lysis_vol":200,"bind_vol":400,"dnase_vol":50,"elution_vol":110,"protocol_filename":"Zymo_Quick-RNA_Cells-Flex_96_Channel"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +from opentrons import protocol_api +import json +import math +from opentrons import types +import numpy as np + +metadata = {"author": "Zach Galluzzo "} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + + +whichwash = 1 + + +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + USE_GRIPPER = True + trash_chute = False + dry_run = True + temp_mod = True + heater_shaker = True + tip_mixing = False # this will only be activated if heater_shaker is False + + sample_vol = 300 + lysis_vol = 200 + bind_vol = 400 + wash_vol = 400 + stop_vol = 500 + dnase_vol = 50 + elution_vol = 110 + + try: + [ + trash_chute, + USE_GRIPPER, + dry_run, + temp_mod, + heater_shaker, + tip_mixing, + wash_vol, + sample_vol, + bind_vol, + lysis_vol, + stop_vol, + dnase_vol, + elution_vol, + ] = get_values( # noqa: F821 + "trash_chute", + "USE_GRIPPER", + "dry_run", + "temp_mod", + "heater_shaker", + "tip_mixing", + "wash_vol", + "sample_vol", + "bind_vol", + "lysis_vol", + "stop_vol", + "dnase_vol", + "elution_vol", + ) + + except NameError: + pass + + # Just to be safe + if heater_shaker: + tip_mixing = False + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + if not dry_run: + settling_time = 3 + else: + settling_time = 0.25 + bead_vol = 30 + binding_buffer_vol = bind_vol + bead_vol # Beads+Binding + starting_vol = sample_vol + lysis_vol # sample volume (300 in shield) + lysis volume + + # Load trash + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") + h_s.close_labware_latch() + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Sample Plate") + + samples_m = sample_plate.wells()[0] + + magblock = ctx.load_module("magneticBlockV1", "C1") + + if temp_mod: + tempdeck = ctx.load_module("Temperature Module Gen2", "A3") + tempblock = tempdeck.load_adapter("opentrons_96_well_aluminum_block") + # Keep elution warm during protocol + elutionplate = tempblock.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate") + + #'#008000','#A52A2A','#00FFFF','#0000FF','#800080','#ADD8E6','#FF0000','#FFFF00','#FF00FF','#00008B','#7FFFD4','#FFC0CB','#FFA500','#00FF00','#C0C0C0' + + # Defining Liquid Reservoirs and Assigning Colors/ Locations + samples = ctx.define_liquid(name="Samples", description="Sample Volume in Shield", display_color="#00FF00") + for well in sample_plate.wells(): + well.load_liquid(liquid=samples, volume=sample_vol) + + lysis_reservoir = ctx.load_labware(deepwell_type, "D2", "Lysis Reservoir") # deleted after use- replaced (by gripper) with wash2 res + lysis_res = lysis_reservoir.wells()[0] # deleted after use- replaced (by gripper) with wash2 res + lysis_buffer = ctx.define_liquid(name="Lysis Buffer", description="Lysis Buffer", display_color="#008000") + for well in lysis_reservoir.wells(): + well.load_liquid(liquid=lysis_buffer, volume=lysis_vol + 100) + + bind_reservoir = ctx.load_labware(deepwell_type, "C3", "Bind Reservoir") + bind_res = bind_reservoir.wells()[0] + bind_buffer = ctx.define_liquid(name="Binding Buffer", description="Binding Buffer", display_color="#A52A2A") + for well in bind_reservoir.wells(): + well.load_liquid(liquid=bind_buffer, volume=bind_vol + 93) + bead_buffer = ctx.define_liquid(name="Beads", description="Magnetic Beads", display_color="#A52A2A") + for well in bind_reservoir.wells(): + well.load_liquid(liquid=bead_buffer, volume=bead_vol + 7) + + wash1_reservoir = ctx.load_labware(deepwell_type, "C2", "Wash 1 Reservoir") + wash1 = wash1_reservoir.wells()[0] + wash1_buffer = ctx.define_liquid(name="Wash 1 Buffer", description="Wash 1 Buffer", display_color="#00FFFF") + for well in wash1_reservoir.wells(): + well.load_liquid(liquid=wash1_buffer, volume=wash_vol + 100) + + wash2_reservoir = magblock.load_labware( + deepwell_type, "Wash 2 Reservoir" + ) # loaded on magplate- move to lysis location after lysis is used + wash2 = wash2_reservoir.wells()[0] # loaded on magplate- move to lysis location after lysis is used + wash2_buffer = ctx.define_liquid(name="Wash 2 Buffer", description="Wash 2 Buffer", display_color="#0000FF") + for well in wash2_reservoir.wells(): + well.load_liquid(liquid=wash2_buffer, volume=wash_vol + 100) + + wash3_reservoir = wash4_reservoir = wash5_reservoir = wash6_reservoir = ctx.load_labware(deepwell_type, "B1", "Wash 3-6 Reservoir") + wash3 = wash4 = wash5 = wash6 = wash3_reservoir.wells()[0] + wash3_buffer = ctx.define_liquid(name="Wash 3-6 Buffers", description="Wash 3-6 Buffers (EtOH)", display_color="#800080") + for well in wash3_reservoir.wells(): + well.load_liquid(liquid=wash3_buffer, volume=(4 * wash_vol) + 100) + + dnase_reservoir = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "B3", "DNAse Reservoir") + dnase_res = dnase_reservoir.wells()[0] + dnase_buffer = ctx.define_liquid(name="DNAseI Buffer", description="DNAseI Buffer", display_color="#ADD8E6") + for well in dnase_reservoir.wells(): + well.load_liquid(liquid=dnase_buffer, volume=dnase_vol + 3) + + stop_reservoir = ctx.load_labware(deepwell_type, "B2", "Stop Reservoir") + stop_res = stop_reservoir.wells()[0] + stop_buffer = ctx.define_liquid(name="Stop Buffer", description="Stop Buffer", display_color="#FF0000") + for well in stop_reservoir.wells(): + well.load_liquid(liquid=stop_buffer, volume=stop_vol + 100) + + elution_res = elutionplate.wells()[0] + elution_buffer = ctx.define_liquid(name="Elution Buffer", description="Elution Buffer", display_color="#FFFF00") + for well in elutionplate.wells(): + well.load_liquid(liquid=elution_buffer, volume=elution_vol) + + # Load tips + tips = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + tips1 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", adapter="opentrons_flex_96_tiprack_adapter").wells()[0] + + # load instruments + pip = ctx.load_instrument("flex_96channel_1000", mount="left") + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + pip.flow_rate.blow_out = 300 + + def blink(): + for i in range(3): + ctx.set_rail_lights(True) + ctx.delay(minutes=0.01666667) + ctx.set_rail_lights(False) + ctx.delay(minutes=0.01666667) + + def remove_supernatant(vol, waste): + pip.pick_up_tip(tips) + if vol > 1000: + x = 2 + else: + x = 1 + transfer_vol = vol / x + for i in range(x): + pip.aspirate(transfer_vol, samples_m.bottom(0.25)) + pip.dispense(transfer_vol, waste.top(-2)) + pip.return_tip() + + # Transfer plate from magnet to H/S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def resuspend_pellet(vol, plate, reps=3): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=1, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0.75, y=0.75, z=1)) + loc3 = plate.bottom().move(types.Point(x=0, y=1, z=1)) + loc4 = plate.bottom().move(types.Point(x=-0.75, y=0.75, z=1)) + loc5 = plate.bottom().move(types.Point(x=-1, y=0, z=1)) + loc6 = plate.bottom().move(types.Point(x=-0.75, y=0 - 0.75, z=1)) + loc7 = plate.bottom().move(types.Point(x=0, y=-1, z=1)) + loc8 = plate.bottom().move(types.Point(x=0.75, y=-0.75, z=1)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc2) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc3) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc4) + pip.dispense(mixvol, loc4) + pip.aspirate(mixvol, loc5) + pip.dispense(mixvol, loc5) + pip.aspirate(mixvol, loc6) + pip.dispense(mixvol, loc6) + pip.aspirate(mixvol, loc7) + pip.dispense(mixvol, loc7) + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc8) + pip.dispense(mixvol, loc8) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + def bead_mix(vol, plate, reps=5): + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 200 + + loc1 = plate.bottom().move(types.Point(x=0, y=0, z=1)) + loc2 = plate.bottom().move(types.Point(x=0, y=0, z=8)) + loc3 = plate.bottom().move(types.Point(x=0, y=0, z=16)) + loc4 = plate.bottom().move(types.Point(x=0, y=0, z=24)) + + if vol > 1000: + vol = 1000 + + mixvol = vol * 0.9 + + for _ in range(reps): + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc2) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc3) + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc4) + if _ == reps - 1: + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 30 + pip.aspirate(mixvol, loc1) + pip.dispense(mixvol, loc1) + + pip.flow_rate.aspirate = 50 + pip.flow_rate.dispense = 150 + + def lysis(vol, source): + pip.pick_up_tip(tips) + pip.aspirate(vol, source) + pip.dispense(vol, samples_m) + resuspend_pellet(350, samples_m, reps=4 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + if not heater_shaker and tip_mixing: + bead_mix(vol, samples_m, reps=5) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker at 2000 rpm for 1 minute.") + + # Delete Lysis reservoir from deck + ctx.move_labware( + lysis_reservoir, "C4" if USE_GRIPPER else protocol_api.OFF_DECK, use_gripper=USE_GRIPPER # put in staging area (off deck) + ) + + # Transfer wash2 res from magnet to deck slot + ctx.move_labware(wash2_reservoir, "D2", use_gripper=USE_GRIPPER) + if heater_shaker: + ctx.delay(minutes=1 if not dry_run else 0.25, msg="Please wait 1 minute while the lysis buffer mixes with the sample.") + h_s.deactivate_shaker() + + def bind(vol, source): + """ + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead bining. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + pip.pick_up_tip(tips) + # Mix in reservoir + bead_mix(vol, source, reps=3 if not dry_run else 1) + # Transfer from reservoir + pip.aspirate(vol, source) + pip.dispense(vol, samples_m) + # Mix in plate + bead_mix(1000, samples_m, reps=4 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=20 if not dry_run else 0.25, msg="Please wait 20 minutes while the sample binds with the beads.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + for x in range(3): + bead_mix(binding_buffer_vol, samples_m, reps=10) + ctx.delay(minutes=2) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker at 1800 rpm for 20 minutes.") + + if heater_shaker: + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for bindi in np.arange(settling_time + 2, 0, -0.5): # Settling time delay with countdown timer + ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") + + # remove initial supernatant + remove_supernatant(vol + starting_vol, bind_res) + + def wash(vol, source): + + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + waste_res = bind_res + h = 1 + if source == wash2: + waste_res = bind_res + h = 1 + if source == wash3: + waste_res = wash2 + h = 30 + if source == wash4: + waste_res = wash2 + h = 20 + if source == wash5: + waste_res = wash2 + h = 10 + if source == wash6: + waste_res = wash2 + h = 1 + + pip.pick_up_tip(tips) + pip.aspirate(vol, source.bottom(h)) + pip.dispense(vol, samples_m.top(-2)) + # resuspend_pellet(vol,samples_m,reps=5 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(2000) + ctx.delay(minutes=5 if not dry_run else 0.25, msg="Please allow 5 minutes for wash to mix on heater-shaker.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(vol, samples_m, reps=12) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 2000 rpm.") + + if heater_shaker: + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for washi in np.arange(settling_time, 0, -0.5): # settling time timer for washes + ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in wash " + str(whichwash) + " incubation.") + + remove_supernatant(vol, waste_res) + + whichwash += 1 + + def dnase(vol, source): + pip.pick_up_tip(tips) + pip.flow_rate.aspirate = 10 + pip.aspirate(vol, source.bottom(0.5)) + ctx.delay(seconds=1) + pip.dispense(vol, samples_m) + if heater_shaker and not dry_run: + h_s.set_target_temperature(65) + resuspend_pellet(vol, samples_m, reps=8 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + # minutes should equal 10 minus time it takes to reach 65 + ctx.delay(minutes=9 if not dry_run else 0.25, msg="Please wait 10 minutes while the dnase incubates.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + for x in range(2): + bead_mix(vol, samples_m, reps=10) + ctx.delay(minutes=1) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 10 minutes at 2000 rpm.") + + def stop_reaction(vol, source): + + pip.pick_up_tip(tips) + pip.aspirate(vol, source) + pip.dispense(vol, samples_m) + # resuspend_pellet(vol,samples_m,reps=2 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1800) + ctx.delay(minutes=10 if not dry_run else 0.1, msg="Please wait 10 minutes while the stop solution inactivates the dnase.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + for x in range(2): + bead_mix(vol, samples_m, reps=10) + ctx.delay(minutes=1) + pip.return_tip() + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 10 minutes at 1800 rpm.") + + if heater_shaker: + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for stop in np.arange(settling_time + 1, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(stop) + " minutes left in this incubation.") + + remove_supernatant(vol + 50, wash1) + + def elute(vol, source): + pip.pick_up_tip(tips1) + # Transfer + pip.aspirate(vol, source) + pip.dispense(vol, samples_m) + # Mix + resuspend_pellet(vol, samples_m, reps=3 if not dry_run else 1) + if not tip_mixing: + pip.return_tip() + + # Elution Incubation + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + if not dry_run and temp_mod: + tempdeck.set_temperature(4) + ctx.delay(minutes=3 if not dry_run else 0.25, msg="Please wait 5 minutes while the sample elutes from the beads.") + h_s.deactivate_shaker() + if not heater_shaker and tip_mixing: + bead_mix(vol, samples_m, reps=9) + pip.return_tip() + if not dry_run and temp_mod: + tempdeck.set_temperature(4) + if not heater_shaker and not tip_mixing: + if not dry_run: + ctx.pause(msg="Place on shaker for 5 minutes at 2000 rpm.") + + if heater_shaker: + h_s.open_labware_latch() + # Transfer plate to magnet + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for elutei in np.arange(settling_time + 2, 0, -0.5): + ctx.delay(minutes=0.5, msg="Incubating on MagDeck for " + str(elutei) + " more minutes.") + + pip.flow_rate.aspirate = 10 + + pip.pick_up_tip(tips1) + pip.aspirate(vol, samples_m) + pip.dispense(vol, source) + pip.return_tip() + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + # Start Protocol + lysis(lysis_vol, lysis_res) + bind(binding_buffer_vol, bind_res) + wash(wash_vol, wash1) + if not dry_run: + wash(wash_vol, wash2) + wash(wash_vol, wash3) + # dnase1 treatment + dnase(dnase_vol, dnase_res) + stop_reaction(stop_vol, stop_res) + # Resume washes + if not dry_run: + wash(wash_vol, wash4) + wash(wash_vol, wash5) + wash(wash_vol, wash6) + if temp_mod: + tempdeck.set_temperature(55) + drybeads = 9 # Number of minutes you want to dry for + else: + drybeads = 0.5 + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + elute(elution_vol, elution_res) diff --git a/app-testing/files/protocols/pl_Zymo_Quick_RNA_Cells_Flex_multi.py b/app-testing/files/protocols/pl_Zymo_Quick_RNA_Cells_Flex_multi.py new file mode 100644 index 00000000000..5dacc5716e0 --- /dev/null +++ b/app-testing/files/protocols/pl_Zymo_Quick_RNA_Cells_Flex_multi.py @@ -0,0 +1,682 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"trash_chute":false,"USE_GRIPPER":true,"dry_run":false,"mount":"left","temp_mod":true,"heater_shaker":true,"res_type":"nest_12_reservoir_15ml","num_samples":8,"wash_vol":500,"stop_vol":500,"wash2_vol":700,"lysis_vol":200,"sample_vol":400,"bind_vol":400,"dnase_vol":50,"elution_vol":100,"protocol_filename":"Zymo_Quick-RNA_Cells-Flex_multi"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons.types import Point +import json +import math +from opentrons import types +import numpy as np + +metadata = { + "author": "Zach Galluzzo ", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + +whichwash = 1 +sample_max = 48 +tip1k = 0 +tip200 = 0 +drop_count = 0 + + +# Start protocol +def run(ctx): + """ + Here is where you can change the locations of your labware and modules + (note that this is the recommended configuration) + """ + trash_chute = False # If this is true, trash chute is loaded in D3, otherwise trash bin is loaded there + USE_GRIPPER = True + dry_run = False + mount = "left" + res_type = "nest_12_reservoir_22ml" + temp_mod = True + heater_shaker = True + + num_samples = 8 + wash_vol = 500 + stop_vol = 500 + wash2_vol = 700 + lysis_vol = 200 + sample_vol = 400 # 400ul shield per sample + bind_vol = 400 + dnase_vol = 50 + elution_vol = 100 + + try: + [ + res_type, + temp_mod, + trash_chute, + USE_GRIPPER, + dry_run, + mount, + num_samples, + heater_shaker, + wash_vol, + stop_vol, + wash2_vol, + lysis_vol, + sample_vol, + bind_vol, + dnase_vol, + elution_vol, + ] = get_values( # noqa: F821 + "res_type", + "temp_mod", + "trash_chute", + "USE_GRIPPER", + "dry_run", + "mount", + "num_samples", + "heater_shaker", + "wash_vol", + "stop_vol", + "wash2_vol", + "lysis_vol", + "sample_vol", + "bind_vol", + "dnase_vol", + "elution_vol", + ) + + except NameError: + pass + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + + if not dry_run: + settling_time = 2 + else: + settling_time = 0.25 + bead_vol = 30 + binding_buffer_vol = bind_vol + bead_vol + starting_vol = sample_vol + lysis_vol + if trash_chute: + trash = ctx.load_waste_chute() + else: + trash = ctx.load_trash_bin("D3") + + if heater_shaker: + h_s = ctx.load_module("heaterShakerModuleV1", "D1") + h_s_adapter = h_s.load_adapter("opentrons_96_deep_well_adapter") + sample_plate = h_s_adapter.load_labware(deepwell_type, "Sample Plate") + h_s.close_labware_latch() + else: + sample_plate = ctx.load_labware(deepwell_type, "D1", "Samples") + + if temp_mod: + tempdeck = ctx.load_module("Temperature Module Gen2", "A3") + temp_block = tempdeck.load_adapter("opentrons_96_well_aluminum_block") + if not dry_run: + tempdeck.set_temperature(4) + elutionplate = temp_block.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "Elution Plate") + else: + elutionplate = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A3", "Elution Plate") + + magblock = ctx.load_module("magneticBlockV1", "C1") + waste = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste").wells()[0].top() + res1 = ctx.load_labware(res_type, "D2", "reagent reservoir 1") + res2 = ctx.load_labware(res_type, "C2", "reagent reservoir 2") + res3 = ctx.load_labware(res_type, "C3", "reagent reservoir 3") + num_cols = math.ceil(num_samples / 8) + + # Load tips and combine all similar boxes + t1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "Tips 1") + t1001 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "Tips 2") + t1002 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B1", "Tips 3") + t1003 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B2", "Tips 4") + t1k = [*t1000.wells()[8 * (num_cols) : 96], *t1001.wells(), *t1002.wells(), *t1003.wells()] + t1k_super = t1000.wells()[: 8 * (num_cols)] + + # load instruments + m1000 = ctx.load_instrument("flex_8channel_1000", mount) + + """ + Here is where you can define the locations of your reagents. + """ + lysis_ = res1.wells()[0] + binding_buffer = res1.wells()[1:3] + elutionsolution = res1.wells()[-1] + + wash1 = res2.wells()[:3] + wash2 = res2.wells()[3:6] + wash3 = res2.wells()[6:9] + stopreaction = res2.wells()[9:] + + wash4 = res3.wells()[:4] + wash5 = res3.wells()[4:8] + wash6 = res3.wells()[8:] + + elution_samples_m = elutionplate.rows()[0][:num_cols] + dnase1 = elutionplate.rows()[0][num_cols : 2 * num_cols] + samples_m = sample_plate.rows()[0][:num_cols] + # Redefine per well for color mapping + cells_ = sample_plate.wells()[: (8 * num_cols)] + dnase1_ = elutionplate.wells()[(8 * num_cols) : (16 * num_cols)] + + colors = [ + "#008000", + "#A52A2A", + "#A52A2A", + "#00FFFF", + "#0000FF", + "#800080", + "#ADD8E6", + "#FF0000", + "#FFFF00", + "#FF00FF", + "#00008B", + "#7FFFD4", + "#FFC0CB", + "#FFA500", + "#00FF00", + "#C0C0C0", + ] + + locations = [lysis_, binding_buffer, binding_buffer, wash1, wash2, wash3, wash4, wash5, wash6, stopreaction, elutionsolution] + vols = [lysis_vol, bead_vol, bind_vol, wash_vol, wash_vol, wash_vol, wash2_vol, wash2_vol, wash2_vol, stop_vol, elution_vol] + liquids = ["Lysis", "Beads", "Binding", "Wash 1", "Wash 2", "Wash 3", "Wash 4", "Wash 5", "Wash 6", "Stop Solution", "Final Elution"] + + cell_samples = ctx.define_liquid(name="Cells", description="Cells in DNA/ RNA Shield", display_color="#C0C0C0") + dnase = ctx.define_liquid(name="DNAseI", description="DNAseI", display_color="#00FF00") + + # Loading liquids per plate well + for i in cells_: + i.load_liquid(liquid=cell_samples, volume=sample_vol) + for i in dnase1_: + i.load_liquid(liquid=dnase, volume=dnase_vol) + + delete = len(colors) - len(liquids) + + if delete >= 1: + for i in range(delete): + colors.pop(-1) + + # Loading liquids per reservoir well + def liquids_(liq, location, color, vol): + sampnum = 8 * (math.ceil(num_samples / 8)) + """ + Takes an individual liquid at a time and adds the color to the well + in the description. + """ + # Volume Calculation + if liq == "Beads": + extra_samples = math.ceil(1500 / bind_vol) + + else: + extra_samples = math.ceil(1500 / vol) + + # Defining and assigning liquids to wells + if isinstance(location, list): + limit = sample_max / len(location) # Calculates samples/ res well + iterations = math.ceil(sampnum / limit) + left = sampnum - limit + if liq == "Wash 4" or liq == "Wash 5" or liq == "Wash 6": + if sampnum <= 8: + samples_per_well = [8] + if 8 < sampnum <= 16: + samples_per_well = [8, 8] + if 16 < sampnum <= 24: + samples_per_well = [8, 8, 8] + if 24 < sampnum <= 32: + samples_per_well = [8, 8, 8, 8] + if 32 < sampnum <= 40: + samples_per_well = [12, 12, 8, 8] + if 40 < sampnum <= 48: + samples_per_well = [12, 12, 12, 12] + else: + while left > limit: + left = left - limit + if left > 0: + last_iteration_samp_num = left + elif left < 0: + last_iteration_samp_num = sampnum + else: + last_iteration_samp_num = limit + + samples_per_well = [] + + for i in range(iterations): + # append the left over on the last iteration + if i == (iterations - 1): + samples_per_well.append(last_iteration_samp_num) + else: + samples_per_well.append(limit) + + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + for sample, well in zip(samples_per_well, location[: len(samples_per_well)]): + v = vol * (sample + extra_samples) + well.load_liquid(liquid=liq, volume=v) + else: + v = vol * (sampnum + extra_samples) + liq = ctx.define_liquid(name=str(liq), description=str(liq), display_color=color) + location.load_liquid(liquid=liq, volume=v) + + for x, (ll, l, c, v) in enumerate(zip(liquids, locations, colors, vols)): + liquids_(ll, l, c, v) + + m1000.flow_rate.aspirate = 300 + m1000.flow_rate.dispense = 300 + m1000.flow_rate.blow_out = 300 + + def tiptrack(pip, tipbox): + global tip1k + global tip200 + global drop_count + if pip == m1000: + pip.pick_up_tip(tipbox[int(tip1k)]) + tip1k = tip1k + 8 + drop_count = drop_count + 8 + if drop_count >= 150: + drop_count = 0 + ctx.pause("Please empty the waste bin of all the tips before continuing.") + + def blink(): + for i in range(3): + ctx.set_rail_lights(True) + ctx.delay(minutes=0.01666667) + ctx.set_rail_lights(False) + ctx.delay(minutes=0.01666667) + + def remove_supernatant(vol): + ctx.comment("-----Removing Supernatant-----") + m1000.flow_rate.aspirate = 30 + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + + def _waste_track(vol): + global waste_vol + waste_vol = waste_vol + (vol * 8) + if waste_vol >= 185000: + m1000.home() + blink() + ctx.pause("Please empty liquid waste before resuming.") + waste_vol = 0 + + for i, m in enumerate(samples_m): + m1000.pick_up_tip(t1k_super[8 * i]) + loc = m.bottom(0.5) + for _ in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, m.top()) + m1000.move_to(m.center()) + m1000.transfer(vol_per_trans, loc, waste, new_tip="never", air_gap=20) + m1000.blow_out(waste) + m1000.air_gap(20) + m1000.drop_tip(t1k_super[8 * i]) + m1000.flow_rate.aspirate = 300 + # Move Plate From Magnet to H-S + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, h_s_adapter if heater_shaker else "D1", use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + def bead_mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top().move(types.Point(x=0, y=0, z=5)) + aspbot = well.bottom().move(types.Point(x=0, y=3, z=1)) + asptop = well.bottom().move(types.Point(x=0, y=-3, z=5)) + disbot = well.bottom().move(types.Point(x=0, y=2, z=3)) + distop = well.top().move(types.Point(x=0, y=1, z=0)) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, aspbot) + pip.dispense(vol, distop) + pip.aspirate(vol, asptop) + pip.dispense(vol, disbot) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, aspbot) + pip.dispense(vol, aspbot) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def mixing(well, pip, mvol, reps=8): + """ + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(1) + disp = well.top(-8) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def lysis(vol, source): + ctx.comment("-----Beginning Lysis-----") + num_transfers = math.ceil(vol / 980) + tiptrack(m1000, t1k) + for i in range(num_cols): + src = source + tvol = vol / num_transfers + for t in range(num_transfers): + m1000.aspirate(tvol, src.bottom(1)) + m1000.dispense(m1000.current_volume, samples_m[i].top(-3)) + + for i in range(num_cols): + if i != 0: + tiptrack(m1000, t1k) + mixing(samples_m[i], m1000, starting_vol, reps=8 if not dry_run else 1) + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay(minutes=2 if not dry_run else 0.1, msg="Please wait 2 minutes while the lysis buffer mixes with the sample.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on a shaker at 2000 rpm for 2 minutes to allow sufficient lysis.") + + def bind(vol): + """ + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead bining. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + latest_chan = -1 + ctx.comment("-----Beginning Bind Steps-----") + for i, well in enumerate(samples_m): + tiptrack(m1000, t1k) + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + source = binding_buffer[i // 3] + if i == 0 or i == 3: + r = 6 + else: + r = 1 + mixing(source, m1000, binding_buffer_vol, reps=r if not dry_run else 1) + for t in range(num_trans): + m1000.transfer(vol_per_trans, source.bottom(1), well.top(), air_gap=20, new_tip="never") + m1000.air_gap(5) + mixing(well, m1000, vol + starting_vol, reps=4 if not dry_run else 1) + m1000.blow_out(well.top(-2)) + m1000.air_gap(5) + m1000.drop_tip() + + ctx.comment("-----Incubating Bind on Heater-Shaker-----") + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1650) + ctx.delay(minutes=20 if not dry_run else 0.1, msg="Please wait 20 minutes while the sample binds with the beads.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 1650 rpm for 20 minutes to allow beads to bind DNA.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for bindi in np.arange(settling_time + 1, 0, -0.5): # Settling time delay with countdown timer + ctx.delay(minutes=0.5, msg="There are " + str(bindi) + " minutes left in the incubation.") + + # remove initial supernatant + remove_supernatant(vol + starting_vol) + + def wash(vol, source): + + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + whichwash = 1 + if source == wash2: + whichwash = 2 + if source == wash3: + whichwash = 3 + if source == wash4: + whichwash = 4 + if source == wash5: + whichwash = 5 + if source == wash6: + whichwash = 6 + + if vol == wash_vol: + speed = 2000 + if vol == wash2_vol: + speed = 1850 + + ctx.comment("-----Beginning Wash #" + str(whichwash) + "-----") + + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + tiptrack(m1000, t1k) + for i, m in enumerate(samples_m): + if vol == wash_vol: + src = source[i // 2] # 3 wells each + if vol == wash2_vol: + if i <= 3: + src = source[i] # 4 wells each + if i == 4: + m1000.aspirate(vol_per_trans / 2, source[0]) + m1000.dispense(vol_per_trans / 2, source[1]) + src = source[1] + if i == 5: + m1000.aspirate(vol_per_trans / 2, source[2]) + m1000.dispense(vol_per_trans / 2, source[3]) + src = source[3] + for n in range(num_trans): + m1000.transfer(vol_per_trans, src.bottom(1), m.top(), air_gap=20, new_tip="never") + m1000.blow_out(m.top()) + m1000.air_gap(10) + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(speed) + ctx.delay(minutes=5 if not dry_run else 0.1, msg="Please allow 5 minutes for wash to mix on heater-shaker.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at " + str(speed) + " rpm for 5 minutes.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for washi in np.arange(settling_time, 0, -0.5): # settling time timer for washes + ctx.delay(minutes=0.5, msg="There are " + str(washi) + " minutes left in wash " + str(whichwash) + " incubation.") + + remove_supernatant(vol) + + def dnase(vol, source): + ctx.comment("-----Beginning DNAseI Steps-----") + num_trans = math.ceil(vol / 200) + vol_per_trans = vol / num_trans + tiptrack(m1000, t1k) + + for i, m in enumerate(samples_m): + src = source + for n in range(num_trans): + m1000.flow_rate.aspirate = 15 + m1000.aspirate(vol, src[i]) + m1000.dispense(vol, m.top(-5)) + m1000.blow_out() + + m1000.flow_rate.aspirate = 300 + + for i in range(num_cols): + if i != 0: + tiptrack(m1000, t1k) + mixing(samples_m[i], m1000, vol, reps=8 if not dry_run else 1) + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + if not dry_run: + h_s.set_and_wait_for_temperature(65) + # minutes should equal 10 minus time it takes to reach 65 + ctx.delay(minutes=5 if not dry_run else 0.1, msg="Please wait 10 minutes while the dnase incubates.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on a heater-shaker at 65C. This should shake at 2000 rpm for 5 minutes.") + + def stop_reaction(vol, source): + ctx.comment("-----Adding Stop Solution to Inactivate DNAseI-----") + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + tiptrack(m1000, t1k) + for i, m in enumerate(samples_m): + src = source[i // 2] + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.transfer(vol_per_trans, src.bottom(1), m.top(), air_gap=20, new_tip="never") + + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=1500) + ctx.delay(minutes=10 if not dry_run else 0.1, msg="Please wait 10 minutes while the stop solution inactivates the dnase.") + h_s.deactivate_shaker() + else: + if not dry_run: + ctx.pause(msg="Place on shaker at 1500 rpm for 10 minutes.") + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for stop in np.arange(settling_time, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(stop) + " minutes left in this incubation.") + + remove_supernatant(vol + 50) + + def elute(vol): + ctx.comment("-----Beginning Elution Steps-----") + tiptrack(m1000, t1k) + for i, m in enumerate(samples_m): + m1000.aspirate(vol, elutionsolution.bottom(1)) + m1000.dispense(m1000.current_volume, m.top(-2)) + m1000.blow_out(m.top(-2)) + + m1000.drop_tip() + + if heater_shaker: + h_s.set_and_wait_for_shake_speed(rpm=2000) + ctx.delay(minutes=5 if not dry_run else 0.1, msg="Please wait 5 minutes while the sample elutes from the beads.") + h_s.deactivate_shaker() + + if heater_shaker: + h_s.open_labware_latch() + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + if heater_shaker: + h_s.close_labware_latch() + + for elutei in np.arange(settling_time, 0, -0.5): + ctx.delay(minutes=0.5, msg="Incubating on MagDeck for " + str(elutei) + " more minutes.") + + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + tiptrack(m1000, t1k) + m1000.flow_rate.aspirate = 75 + m1000.flow_rate.dispense = 150 + m1000.transfer(100, m.bottom(0.15), e.bottom(5), air_gap=20, new_tip="never") + m1000.blow_out(e.top(-2)) + m1000.air_gap(10) + m1000.drop_tip() + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + lysis(lysis_vol, lysis_) + bind(binding_buffer_vol) + wash(wash_vol, wash1) + if not dry_run: + wash(wash_vol, wash2) + wash(wash_vol, wash3) + # dnase1 treatment + dnase(dnase_vol, dnase1) + stop_reaction(stop_vol, stopreaction) + + if not dry_run: + # Resume washes + wash(wash2_vol, wash4) + wash(wash2_vol, wash5) + wash(wash2_vol, wash6) + drybeads = 10 # Number of minutes you want to dry for + else: + drybeads = 0.5 + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay(minutes=0.5, msg="There are " + str(beaddry) + " minutes left in the drying step.") + elute(elution_vol) diff --git a/app-testing/files/protocols/pl_cherrypicking_csv_airgap.py b/app-testing/files/protocols/pl_cherrypicking_csv_airgap.py new file mode 100644 index 00000000000..7c4e157c775 --- /dev/null +++ b/app-testing/files/protocols/pl_cherrypicking_csv_airgap.py @@ -0,0 +1,221 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"csv_samp":"Date,6/6/2013,,Experiment,,,,,,,,\\nTotal Number of Samples,,,37,,,10,Final concentration (ng/µL),,,\\"Source deck locations A2, A3, B4, C4\\",\\nNumber of Columns,,,5,Do not change this value.,,40,Final volume (µL),,,,\\n,,,,,,,,,,,\\nSource Location,Source Well,Source Volume ,Buffer Volume,Destination Location,Destination Well,Start Concentration,Sample,,Source Plates,Location,\\nA2,,0.0,40.0,A4,A1,0,NTC,,,A2,Q20230905B\\nA2,F02,7.1,32.9,A4,B1,55.99,2692,,,A3,Q20230831A\\nA2,H02,8.2,31.8,A4,C1,48.52,2694,,,B4,\\nA2,F03,6.5,33.5,A4,D1,61.95,2700,,,C4,\\nA2,H03,6.3,33.7,A4,E1,63.95,2702,,,,\\nA2,,0.0,40.0,A4,F1,,NTC,,Destination Plate,,\\nA2,D04,7.5,32.5,A4,G1,53.64,2705,,,,\\nA2,E04,6.0,34.0,A4,H1,66.67,2706,,,,\\nA2,H04,6.9,33.1,A4,A2,57.57,2709,,,,\\nA2,A05,5.6,34.4,A4,B2,71.92,2710,,,,\\nA2,C05,6.0,34.0,A4,C2,66.29,2712,,,,\\nA2,D05,7.7,32.3,A4,D2,51.91,2713,,,,\\nA2,E05,5.2,34.8,A4,E2,77.09,2714,,,,\\nA2,F05,6.1,33.9,A4,F2,65.5,2715,,,,\\nA2,G05,7.7,32.3,A4,G2,51.68,2716,,,,\\nA2,G06,5.3,34.7,A4,H2,75.08,2724,,,,\\nA2,B07,8.0,32.0,A4,A3,49.97,2725,,,,\\nA2,C07,8.5,31.5,A4,B3,46.98,2726,,,,\\nA2,E07,8.0,32.0,A4,C3,49.81,2728,,,,\\nA2,C08,8.2,31.8,A4,D3,48.63,2734,,,,\\nA2,F08,8.6,31.4,A4,E3,46.49,2737,,,,\\n,,,,A4,F3,,,,,,\\n,,,,A4,G3,,,,,,\\n,,,,A4,H3,,,,,,\\nB2,,0.0,40.0,A4,A4,0,NTC,,,,\\nB2,B01,6.1,33.9,A4,B4,65.12,2635,,,,\\nB2,C01,7.7,32.3,A4,C4,51.9,2636,,,,\\nB2,D01,7.8,32.2,A4,D4,51.04,2637,,,,\\nB2,E01,6.9,33.1,A4,E4,58,2638,,,,\\nB2,F01,7.5,32.5,A4,F4,53.14,2639,,,,\\nB2,H01,5.4,34.6,A4,G4,74.64,2641,,,,\\nB2,A02,5.8,34.2,A4,H4,68.6,2642,,,,\\nB2,B02,6.1,33.9,A4,A5,65.82,2643,,,,\\nB2,C02,8.0,32.0,A4,B5,49.69,2644,,,,\\nB2,D02,6.7,33.3,A4,C5,59.35,2645,,,,\\nB2,E02,8.2,31.8,A4,D5,48.86,2646,,,,\\nB2,F02,5.0,35.0,A4,E5,79.33,2647,,,,\\nB2,G02,6.6,33.4,A4,F5,60.8,2648,,,,\\nB2,,,,A4,G5,,,,,,\\nB2,,,,A4,H5,,,,,,\\n,,,,A4,A6,,,,,,\\n,,,,A4,B6,,,,,,\\n,,,,A4,C6,,,,,,\\n,,,,A4,D6,,,,,,\\n,,,,A4,E6,,,,,,\\n,,,,A4,F6,,,,,,\\n,,,,A4,G6,,,,,,\\n,,,,A4,H6,,,,,,\\n,,,,A4,A7,,,,,,\\n,,,,A4,B7,,,,,,\\n,,,,A4,C7,,,,,,\\n,,,,A4,D7,,,,,,\\n,,,,A4,E7,,,,,,\\n,,,,A4,F7,,,,,,\\n,,,,A4,G7,,,,,,\\n,,,,A4,H7,,,,,,\\n,,,,A4,A8,,,,,,\\n,,,,A4,B8,,,,,,\\n,,,,A4,C8,,,,,,\\n,,,,A4,D8,,,,,,\\n,,,,A4,E8,,,,,,\\n,,,,A4,F8,,,,,,\\n,,,,A4,G8,,,,,,\\n,,,,A4,H8,,,,,,\\n,,,,A4,A9,,,,,,\\n,,,,A4,B9,,,,,,\\n,,,,A4,C9,,,,,,\\n,,,,A4,D9,,,,,,\\n,,,,A4,E9,,,,,,\\n,,,,A4,F9,,,,,,\\n,,,,A4,G9,,,,,,\\n,,,,A4,H9,,,,,,\\n,,,,A4,A10,,,,,,\\n,,,,A4,B10,,,,,,\\n,,,,A4,C10,,,,,,\\n,,,,A4,D10,,,,,,\\n,,,,A4,E10,,,,,,\\n,,,,A4,F10,,,,,,\\n,,,,A4,G10,,,,,,\\n,,,,A4,H10,,,,,,\\n,,,,A4,A11,,,,,,\\n,,,,A4,B11,,,,,,\\n,,,,A4,C11,,,,,,\\n,,,,A4,D11,,,,,,\\n,,,,A4,E11,,,,,,\\n,,,,A4,F11,,,,,,\\n,,,,A4,G11,,,,,,\\n,,,,A4,H11,,,,,,\\n,,,,A4,A12,,,,,,\\n,,,,A4,B12,,,,,,\\n,,,,A4,C12,,,,,,\\n,,,,A4,D12,,,,,,\\n,,,,A4,E12,,,,,,\\n,,,,A4,F12,,,,,,\\n,,,,A4,G12,,,,,,\\n,,,,A4,H12,,,,,,\\n","asp_rate":1,"disp_rate":1,"p50_mount":"right","protocol_filename":"cherrypicking-csv-airgap"}""" + ) + return [_all_values[n] for n in names] + + +# flake8: noqa +from collections import defaultdict + +from opentrons import protocol_api +from opentrons import types +import random +import math + +metadata = { + "ctx.Name": "Cherrypicking Protocol", + "author": "Rami Farawi 0 and source_slot_string and dest_well_string: + if source_well_string[1] == "0": + new_source_string = source_well_string[0] + source_well_string[2] + # print(new_source_string, dest_well_string, source_slot_string, dest_slot_string) + # print(ctx.deck[source_slot_string]) + p50.pick_up_tip() + p50.aspirate(dil_vol, water) + p50.air_gap(airgap) + p50.aspirate(source_vol, ctx.deck[source_slot_string][new_source_string], rate=asp_rate) + p50.dispense(source_vol + airgap + dil_vol, dest_plate[dest_well_string], rate=disp_rate) + p50.mix(4, (source_vol + airgap + dil_vol) * 0.4, dest_plate[dest_well_string]) + p50.blow_out(dest_plate[dest_well_string].top(z=-3)) + p50.drop_tip() + + samples_liq = ctx.define_liquid( + name="Samples", + description="Samples", + display_color="#7EFF42", + ) + + for plate in source_plate: + for well in plate.wells()[::2]: + well.load_liquid(liquid=samples_liq, volume=200) + + dil_liq = ctx.define_liquid( + name="Diluent", + description="Diluent", + display_color="#50D5FF", + ) + + res["A1"].load_liquid(liquid=dil_liq, volume=200) diff --git a/app-testing/files/protocols/pl_cherrypicking_flex_v3.py b/app-testing/files/protocols/pl_cherrypicking_flex_v3.py new file mode 100644 index 00000000000..8eb22772a2b --- /dev/null +++ b/app-testing/files/protocols/pl_cherrypicking_flex_v3.py @@ -0,0 +1,89 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"transfer_csv":"Source Labware,Source Slot,Source Well,Source Aspiration Height Above Bottom (in mm),Dest Labware,Dest Slot,Dest Well,Volume (in ul)\\nagilent_1_reservoir_290ml,1,A1,1,nest_96_wellplate_100ul_pcr_full_skirt,4,A11,1\\nnest_12_reservoir_15ml,2,A1,1,nest_96_wellplate_2ml_deep,5,A5,3\\nnest_1_reservoir_195ml,3,A1,1,nest_96_wellplate_2ml_deep,5,H12,7\\n","pipette_type":"flex_1channel_50","pipette_mount":"right","tip_type":"standard_50","tip_reuse":"always","protocol_filename":"Uni of Montana_Cherrypicking_Flex_for library upload_v13"}""" + ) + return [_all_values[n] for n in names] + + +from opentrons import protocol_api + +metadata = { + "ctx.Name": "Uni of Montana Cherrypicking_Flex", + "author": "Krishna Soma ", +} +requirements = {"robotType": "OT-3", "apiLevel": "2.15"} + + +def run(ctx: protocol_api.ProtocolContext): + + [pipette_type, pipette_mount, tip_type, tip_reuse, transfer_csv] = get_values( # noqa: F821 + "pipette_type", "pipette_mount", "tip_type", "tip_reuse", "transfer_csv" + ) + + tiprack_map = { + "flex_1channel_50": { + "standard_50": "opentrons_flex_96_tiprack_50ul", + "filter_50": "opentrons_flex_96_filtertiprack_50ul", + }, + "flex_1channel_1000": { + "standard_1000": "opentrons_flex_96_tiprack_1000ul", + "filter_1000": "opentrons_flex_96_filtertiprack_1000ul", + "standard_200": "opentrons_flex_96_tiprack_200ul", + "filter_200": "opentrons_flex_96_filtertiprack_200ul", + "standard_50": "opentrons_flex_96_tiprack_50ul", + "filter_50": "opentrons_flex_96_filtertiprack_50ul", + }, + } + + # load labware + transfer_info = [[val.strip().lower() for val in line.split(",")] for line in transfer_csv.splitlines() if line.split(",")[0].strip()][ + 1: + ] + for line in transfer_info: + s_lw, s_slot, d_lw, d_slot = line[:2] + line[4:6] + for slot, lw in zip([s_slot, d_slot], [s_lw, d_lw]): + if not int(slot) in ctx.loaded_labwares: + ctx.load_labware(lw.lower(), slot) + + # load tipracks in remaining slots + tiprack_type = tiprack_map[pipette_type][tip_type] + tipracks = [] + for slot in range(4, 12): + if slot not in ctx.loaded_labwares: + tipracks.append(ctx.load_labware(tiprack_type, str(slot))) + + # load pipette + pip = ctx.load_instrument(pipette_type, pipette_mount, tip_racks=tipracks) + + tip_count = 0 + tip_max = len(tipracks * 96) + + def pick_up(): + nonlocal tip_count + if tip_count == tip_max: + ctx.pause("Please refill tipracks before resuming.") + pip.reset_tipracks() + tip_count = 0 + pip.pick_up_tip() + tip_count += 1 + + def parse_well(well): + letter = well[0] + number = well[1:] + return letter.upper() + str(int(number)) + + if tip_reuse == "never": + pick_up() + for line in transfer_info: + _, s_slot, s_well, h, _, d_slot, d_well, vol = line[:8] + source = ctx.loaded_labwares[int(s_slot)].wells_by_name()[parse_well(s_well)].bottom(float(h)) + dest = ctx.loaded_labwares[int(d_slot)].wells_by_name()[parse_well(d_well)] + if tip_reuse == "always": + pick_up() + pip.transfer(float(vol), source, dest, new_tip="never") + if tip_reuse == "always": + pip.drop_tip() + if pip.has_tip: + pip.drop_tip() diff --git a/app-testing/files/protocols/pl_langone_pt2_ribo.py b/app-testing/files/protocols/pl_langone_pt2_ribo.py new file mode 100644 index 00000000000..046efdf1583 --- /dev/null +++ b/app-testing/files/protocols/pl_langone_pt2_ribo.py @@ -0,0 +1,479 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"num_samp":8,"num_pcr_cycles":15,"real_mode":true,"m50_mount":"left","m200_mount":"right","protocol_filename":"langone_pt2_ribo"}""" + ) + return [_all_values[n] for n in names] + + +# flake8: noqa +import math + +from opentrons import protocol_api +from opentrons import types +import random +import math + +metadata = { + "ctx.Name": "RNA seq RiboZero Plus - Part 2", + "author": "Rami Farawi 37: + tc_mod.set_block_temperature(temp) + ctx.delay(seconds=10) + temp -= 1 + tc_mod.open_lid() + + # DEPLETE RNA - 2 + ctx.comment("\n\n----------ADDING RDB----------\n") + for col in sample_wells: + pick_up(m50) + m50.configure_for_volume(5) + m50.aspirate(5, rdb) + m50.dispense(5, col) + m50.mix(5, 15, col) + m50.move_to(col.top()) + ctx.delay(seconds=1) + m50.blow_out(col.top()) + m50.touch_tip() + m50.drop_tip() if real_mode else m50.return_tip() + + if real_mode: + + profile1 = [ + {"temperature": 37, "hold_time_minutes": 15}, + ] + + tc_mod.close_lid() + tc_mod.execute_profile(steps=profile1, repetitions=1, block_max_volume=20) + tc_mod.set_block_temperature(4) + tc_mod.open_lid() + + # REMOVE PROBES - 3 + ctx.comment("\n\n----------ADDING PRB----------\n") + for col in sample_wells: + pick_up(m50) + m50.aspirate(10, prb) + m50.dispense(10, col) + m50.mix(5, 20, col) + m50.move_to(col.top()) + ctx.delay(seconds=1) + m50.blow_out(col.top()) + m50.touch_tip() + m50.drop_tip() if real_mode else m50.return_tip() + + if real_mode: + + profile1 = [{"temperature": 37, "hold_time_minutes": 15}, {"temperature": 70, "hold_time_minutes": 15}] + + tc_mod.close_lid() + tc_mod.execute_profile(steps=profile1, repetitions=1, block_max_volume=30) + tc_mod.set_block_temperature(4) + tc_mod.open_lid() + + # -- Move the plate to empty slot. + ctx.move_labware(labware=dest_plate, new_location=2, use_gripper=True) + + tc_mod.set_block_temperature(21) # ambient + + ctx.move_labware(labware=fresh_dest, new_location=tc_mod, use_gripper=False) + + # BEADS AND WASH - 4 + ctx.comment("\n\n----------ADDING BEADS----------\n") + pick_up(m200) + m200.mix(20, 200, beads) + for col in sample_wells: + if not m200.has_tip: + pick_up(m200) + + m200.aspirate(60, beads) + slow_tip_withdrawal(m200, beads) + m200.dispense(60, col) + m200.mix(10, 70, col) + remove_liq(m200, col) + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.delay(minutes=5 if real_mode else 0.5) + + # -- Move the plate to empty slot. + ctx.move_labware(labware=dest_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if real_mode else 0.5) + ctx.comment("\n\n----------REMOVE SUPER----------\n") + super_vol = 90 + extra = 10 + for col in sample_wells: + pick_up(m200) + m200.aspirate(super_vol, col, rate=0.15) + m200.aspirate(extra, col.bottom(z=0.8), rate=0.15) + slow_tip_withdrawal(m200, col) + m200.dispense(super_vol + extra, trash) + ctx.delay(seconds=2) + m200.blow_out() + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.comment("\n\n----------TWO WASHES----------\n") + super_vol = 150 + extra = 10 + for _ in range(2): + pick_up(m200) + for col in sample_wells: + m200.aspirate(150, ethanol) + slow_tip_withdrawal(m200, ethanol) + m200.dispense(150, col.top()) + m200.move_to(dest_plate.wells()[0].top(z=20)) + ctx.delay(seconds=30) + for col in sample_wells: + if not m200.has_tip: + pick_up(m200) + + m200.aspirate(super_vol, col, rate=0.15) + m200.aspirate(extra, col.bottom(z=0.8), rate=0.15) + slow_tip_withdrawal(m200, col) + m200.dispense(super_vol + extra, trash) + ctx.delay(seconds=2) + m200.blow_out() + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.delay(minutes=2 if real_mode else 0.5) + + ctx.comment("\n\n----------ADDING ELUTION BUFFER----------\n") + # -- Move the plate to empty slot. + ctx.move_labware(labware=dest_plate, new_location=2, use_gripper=True) + + for col in sample_wells: + pick_up(m50) + m50.aspirate(10.5, elution_buff) + m50.dispense(10.5, col) + for _ in range(7): # resuspend beads + m50.aspirate(7, col.bottom(z=0.8)) + m50.dispense(7, col.bottom(z=6), rate=2) + m50.mix(5, 7, col.bottom(z=0.7), rate=1.5) + m50.move_to(col.top()) + ctx.delay(seconds=1) + m50.blow_out(col.top()) + m50.touch_tip() + m50.drop_tip() if real_mode else m50.return_tip() + + # -- Move the plate to empty slot. + ctx.move_labware(labware=dest_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=1.5 if real_mode else 0.5) + + ctx.comment("\n\n----------ADDING EPH3----------\n") + pick_up(m50) + for col in fresh_sample_wells: + m50.aspirate(8.5, eph3) + m50.dispense(8.5, col) + m50.move_to(col.top()) + m50.blow_out(col.top()) + m50.drop_tip() if real_mode else m50.return_tip() + + ctx.comment("\n\n----------TRANSFERRING ELUTE----------\n") + + for s, d in zip(sample_wells, fresh_sample_wells): + pick_up(m50) + m50.aspirate(8.5, s.bottom(z=0.8), rate=0.2) + m50.dispense(8.5, d) + m50.move_to(s.top()) + m50.blow_out(d.top()) + m50.drop_tip() if real_mode else m50.return_tip() + + ctx.move_labware(labware=dest_plate, new_location=protocol_api.OFF_DECK, use_gripper=False) + + if real_mode: + + profile1 = [ + {"temperature": 94, "hold_time_minutes": 1}, + ] + + tc_mod.close_lid() + tc_mod.execute_profile(steps=profile1, repetitions=1, block_max_volume=17) + tc_mod.set_block_temperature(4) + tc_mod.open_lid() + + ctx.comment("\n\n----------ADDING FSA----------\n") + for col in fresh_sample_wells: + pick_up(m50) + m50.aspirate(8, fsa) + m50.dispense(8, col) + m50.mix(5, 19, col) + m50.move_to(col.top()) + ctx.delay(seconds=1) + m50.blow_out(col.top()) + m50.touch_tip() + m50.drop_tip() if real_mode else m50.return_tip() + + if real_mode: + + profile1 = [ + {"temperature": 25, "hold_time_minutes": 10}, + {"temperature": 42, "hold_time_minutes": 15}, + {"temperature": 70, "hold_time_minutes": 15}, + ] + + tc_mod.close_lid() + tc_mod.execute_profile(steps=profile1, repetitions=1, block_max_volume=25) + tc_mod.set_block_temperature(4) + tc_mod.open_lid() + + ctx.comment("\n\n----------ADDING SMM----------\n") + for col in fresh_sample_wells: + pick_up(m50) + m50.aspirate(25, smm) + m50.dispense(25, col) + m50.mix(5, 40, col) + m50.move_to(col.top()) + ctx.delay(seconds=1) + m50.blow_out(col.top()) + m50.touch_tip() + m50.drop_tip() if real_mode else m50.return_tip() + + if real_mode: + tc_mod.set_lid_temperature(40) + + profile1 = [{"temperature": 16, "hold_time_minutes": 60}] + + tc_mod.close_lid() + tc_mod.execute_profile(steps=profile1, repetitions=1, block_max_volume=50) + tc_mod.set_block_temperature(4) + tc_mod.open_lid() + + # -- Move the plate to empty slot. + ctx.move_labware(labware=fresh_dest, new_location=2, use_gripper=True) + + tc_mod.set_block_temperature(21) # ambient + + ctx.move_labware(labware=final_dest, new_location=tc_mod, use_gripper=False) + + # BEADS AND WASH - 4 + ctx.comment("\n\n----------ADDING BEADS----------\n") + pick_up(m200) + m200.mix(20, 200, beads2) + for col in fresh_sample_wells: + if not m200.has_tip: + pick_up(m200) + + m200.aspirate(90, beads2) + slow_tip_withdrawal(m200, beads2) + m200.dispense(90, col) + m200.mix(10, 100, col) + remove_liq(m200, col) + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.delay(minutes=5 if real_mode else 0.5) + + # -- Move the plate to empty slot. + ctx.move_labware(labware=fresh_dest, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=3 if real_mode else 0.5) + ctx.comment("\n\n----------REMOVE SUPER----------\n") + super_vol = 140 + extra = 10 + for col in fresh_sample_wells: + pick_up(m200) + m200.aspirate(super_vol, col, rate=0.15) + m200.aspirate(extra, col.bottom(z=0.8), rate=0.15) + slow_tip_withdrawal(m200, col) + m200.dispense(super_vol + extra, trash) + ctx.delay(seconds=2) + m200.blow_out() + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.comment("\n\n----------TWO WASHES----------\n") + super_vol = 150 + extra = 10 + for _ in range(2): + pick_up(m200) + for col in fresh_sample_wells: + m200.aspirate(150, ethanol2) + slow_tip_withdrawal(m200, ethanol2) + m200.dispense(150, col.top()) + m200.move_to(fresh_dest.wells()[0].top(z=20)) + ctx.delay(seconds=30) + for col in fresh_sample_wells: + if not m200.has_tip: + pick_up(m200) + + m200.aspirate(super_vol, col, rate=0.15) + m200.aspirate(extra, col.bottom(z=0.8), rate=0.15) + slow_tip_withdrawal(m200, col) + m200.dispense(super_vol + extra, trash) + ctx.delay(seconds=2) + m200.blow_out() + m200.drop_tip() if real_mode else m200.return_tip() + + ctx.delay(minutes=2 if real_mode else 0.5) + + ctx.comment("\n\n----------ADDING ELUTION BUFFER----------\n") + # -- Move the plate to empty slot. + ctx.move_labware(labware=fresh_dest, new_location=2, use_gripper=True) + + for col in fresh_sample_wells: + pick_up(m50) + m50.aspirate(19.5, rsb) + m50.dispense(19.5, col) + for _ in range(7): # resuspend beads + m50.aspirate(15, col.bottom(z=0.8)) + m50.dispense(15, col.bottom(z=6), rate=2) + m50.mix(5, 14, col.bottom(z=0.7), rate=1.5) + m50.move_to(col.top()) + ctx.delay(seconds=1) + m50.blow_out(col.top()) + m50.touch_tip() + m50.drop_tip() if real_mode else m50.return_tip() + + # -- Move the plate to empty slot. + ctx.move_labware(labware=fresh_dest, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=2 if real_mode else 0.5) + + ctx.comment("\n\n----------TRANSFERRING ELUTE----------\n") + + for s, d in zip(fresh_sample_wells, final_sample_wells): + pick_up(m50) + m50.aspirate(17.5, s.bottom(z=0.8), rate=0.2) + m50.dispense(17.5, d) + m50.move_to(s.top()) + m50.blow_out(d.top()) + m50.drop_tip() if real_mode else m50.return_tip() diff --git a/app-testing/files/protocols/pl_microBioID_beads_touchtip.py b/app-testing/files/protocols/pl_microBioID_beads_touchtip.py new file mode 100644 index 00000000000..f8bbe8f914e --- /dev/null +++ b/app-testing/files/protocols/pl_microBioID_beads_touchtip.py @@ -0,0 +1,410 @@ +def get_values(*names): + import json + + _all_values = json.loads( + """{"num_samp":8,"digest_time_hours":12,"bead_incubation":15,"test_mode":false,"p50_mount":"right","m50_mount":"left","protocol_filename":"microBioID_beads_touchtip"}""" + ) + return [_all_values[n] for n in names] + + +# flake8: noqa + +from opentrons import protocol_api +from opentrons import protocol_engine +from opentrons import types +import random +import math + +metadata = { + "ctx.Name": "microBioID Protocol", + "author": "Rami Farawi ", +} +requirements = {"robotType": "OT-3", "apiLevel": "2.15"} + +tiprack_slots = ["A1", "A2", "B1", "B2"] + + +def transpose_matrix(m): + return [[r[i] for r in reversed(m)] for i in range(len(m[0]))] + + +def flatten_matrix(m): + return [cell for row in m for cell in row] + + +def well_csv_to_list(csv_string): + """ + Takes a csv string and flattens it to a list, re-ordering to match + Opentrons well order convention (A1, B1, C1, ..., A2, B2, B2, ...) + """ + data = [line.split(",") for line in reversed(csv_string.split("\n")) if line.strip() if line] + if len(data[0]) > len(data): + return flatten_matrix(transpose_matrix(data)) + return flatten_matrix(data) + + +def run(protocol): + [volumes_csv, pip_model, pip_mount, plate_type, res_type, filter_tip, tip_reuse] = get_values( + "volumes_csv", "pip_model", "pip_mount", "plate_type", "res_type", "filter_tip", "tip_reuse" + ) + + # create labware + plate = protocol.load_labware(plate_type, "C1") + reservoir = protocol.load_labware(res_type, "C2") + source = reservoir.wells()[0] + pip_size = pip_model.split("_")[2] + print("-----------") + print(pip_model) + print("-----------") + pip_size = "1000" if pip_size == "50" else pip_size + tip_name = "opentrons_flex_96_tiprack_" + pip_size + "ul" + if filter_tip == "yes": + pip_size = "200" if pip_size == "1000" else pip_size + tip_name = "opentrons_flex_96_filtertiprack_" + pip_size + "ul" + + tipracks = [protocol.load_labware(tip_name, slot) for slot in tiprack_slots] + + pipette = protocol.load_instrument(pip_model, pip_mount, tip_racks=tipracks) + + # create volumes list + volumes = [float(cell) for cell in well_csv_to_list(volumes_csv)] + + for vol in volumes: + if vol < pipette.min_volume: + protocol.comment("WARNING: volume {} is below pipette's minimum volume.".format(vol)) + + if tip_reuse == "never": + pipette.pick_up_tip() + + for vol, dest in zip(volumes, plate.wells()): + if vol > 0: + pipette.transfer(vol, source, dest, new_tip=tip_reuse) + + if pipette.has_tip: + pipette.drop_tip() diff --git a/app-testing/files/protocols/pl_sample_dilution_with_96_channel_pipette.py b/app-testing/files/protocols/pl_sample_dilution_with_96_channel_pipette.py new file mode 100644 index 00000000000..e5690515c51 --- /dev/null +++ b/app-testing/files/protocols/pl_sample_dilution_with_96_channel_pipette.py @@ -0,0 +1,94 @@ +# flake8: noqa + +from opentrons import protocol_api +from opentrons.protocol_api import COLUMN + + +metadata = { + "ctx.Name": "microBioID Protocol", + "author": "Rami Farawi = 8000: + ethanol_well_ctr += 1 + ethanol_ctr = 0 + + def add_reagent(vol, reagent, mix_vol, sample_list, overflow=False, mix=True): + + if not overflow: + for d in sample_list: + pick_up(m50) + m50.aspirate(vol, reagent) + m50.dispense(vol, d) + if mix: + m50.mix(10 if real_mode else 1, mix_vol, d) + m50.move_to(d.top()) + ctx.delay(seconds=1) + m50.blow_out(d.top(-3)) + m50.drop_tip() if real_mode else m50.return_tip() + + def adding_eb(eb_vol, eb_well, list_to_go, eb_mix_vol, first_eb=False): + if first_eb: + half_of_the_columns = math.floor(num_col / 2) + for i, d in enumerate(list_to_go): + pick_up(m50) + m50.aspirate(10, eb_well.top()) + m50.aspirate(eb_vol, eb_well.bottom(z=3 if i < half_of_the_columns else 1)) + m50.dispense(eb_vol, d) + m50.mix(15, eb_mix_vol, d.bottom(z=1), rate=1.2) + m50.dispense(10, d.bottom(z=5)) + ctx.delay(seconds=2) + m50.blow_out() + m50.drop_tip() if real_mode else m50.return_tip() + else: + for d in list_to_go: + pick_up(m50) + m50.aspirate(10, eb_well.top()) + m50.aspirate(eb_vol, eb_well) + m50.dispense(eb_vol, d) + m50.mix(15, eb_mix_vol, d.bottom(z=1), rate=1.2) + m50.dispense(10, d.top(z=5)) + ctx.delay(seconds=2) + m50.blow_out() + m50.drop_tip() if real_mode else m50.return_tip() + + def pause(msg): + ctx.comment("\n") + ctx.pause(msg) + ctx.comment("\n") + + ########################################################################## + ########################################################################## + ########################################################################## + ########################################################################## + ############################### REAGENTS ################################## + ########################################################################## + ########################################################################## + ########################################################################## + ########################################################################## + + bead_gap = 15 + + samples = deep_plate.rows()[0][:num_col] + + # deepwell + beads = reag_plate["A1"] + fast_bind_buffer = reag_plate.rows()[0][1:5] # A2, A3, A4, A5 + wash_buffer_1 = wash_plate.rows()[0][0:2] + wash_buffer_2 = wash_plate.rows()[0][2:5] + water = reag_plate["A6"] + beads2 = reag_plate["A7"] + ethanol = reag_plate.rows()[0][7:] + + # pcr PLATE + primers = temp_reag_plate["A1"] + amp_mix = temp_reag_plate["A2"] + + total_ethanol = 150 * num_samp * 2 + num_ethanol_wells = math.ceil(total_ethanol / 10000) + ctx.pause( + f""" + You will need {num_ethanol_wells} ethanol wells in the deepwell plate all with 10mL each, starting at A7, B2. + """ + ) + ctx.pause(f"You will need {100*num_col*1.15} of strep beads in column 1 of the deepwell reagent plate on deck, B2.") + ctx.pause( + f"You will need {200*num_col*1.15} of fast binding buffer in columns A2, A3, A4, A5 of the deepwell reagent plate on deck, B2." + ) + ctx.pause(f"You will need water in A6 of the deepwell reagent plate on deck, B2.") + ctx.pause(f"You will need {90*num_col*1.15} of purification beads in column A7 of the deepwell reagent plate on deck, B2.") + ctx.pause( + f"You will need {200*num_col*1.15} of wash buffer 1 in columns 1 and 2 of the deepwell reagent plate on temperature module, D1" + ) + ctx.pause( + f"You will need {200*num_col*1.15} of wash buffer 2 in columns 3, 4, 5 of the deepwell reagent plate on temperature module, D1" + ) + ctx.pause(f"You will need {2.5*num_col*1.15} of primer in column 1 of the pcr plate on cold aluminum block, A2") + ctx.pause(f"You will need {25*num_col*1.15} of amp mix in column 2 of the pcr plate on cold aluminum block, A2") + + ######################################################################### + ######################### STREP BEAD STUFF ######################### + ######################################################################### + + ctx.comment("\n\n----------Adding Beads to Plate----------\n") + adding_beads(100, samples, which_beads=beads, mix_vol=10, mix_with_beads=False, premix=True) + + for i in range(3): + + if i == 1 and not real_mode: + continue + + ctx.comment("\n\n----------Adding Fast Binding Buffer----------\n") + for d in samples: + pick_up(m200) + m200.aspirate(200, fast_bind_buffer[i]) + m200.dispense(200, d) + m200.mix(15, 150, d, rate=0.3) + slow_tip_withdrawal(m200, d) + ctx.delay(seconds=1) + m200.blow_out(d.top(-3)) + m200.drop_tip() if real_mode else m200.return_tip() + ctx.comment("\n") + + # -- MOVE DEEPWELL TO MAG BLOCK + ctx.move_labware(labware=deep_plate, new_location=mag_block, use_gripper=True, drop_offset={"x": 0, "y": 0, "z": -4}) + + ctx.delay(minutes=1.5 if bead_mode else 0.5) + + if i == 0: + for d in samples: + pick_up(m200) + m200.aspirate(150, d) + dispose_waste(150, m200) + m200.aspirate(150, d.bottom(z=0.6)) + dispose_waste(150, m200) + m200.drop_tip() if real_mode else m200.return_tip() + ctx.comment("\n") + + else: + remove_super(m200, 200, samples, round=0 if i <= 1 else 1, ethanol=True) + + # -- MOVE DEEPWELL TO DECK + ctx.move_labware(labware=deep_plate, new_location=5, use_gripper=True) + + ctx.comment("\n\n----------Adding Fast Binding Buffer----------\n") + for d in samples: + pick_up(m200) + m200.aspirate(200, fast_bind_buffer[3]) + m200.dispense(200, d) + m200.mix(15, 150, d, rate=0.3) + slow_tip_withdrawal(m200, d) + ctx.delay(seconds=1) + m200.blow_out(d.top(-3)) + m200.drop_tip() if real_mode else m200.return_tip() + ctx.comment("\n") + + pause("Combine sample and strep beads for 16 hour hybridization. Place back on slot C2") + + # starting this step from slot 5 + + # -- MOVE DEEPWELL TO MAG BLOCK + ctx.move_labware(labware=deep_plate, new_location=mag_block, use_gripper=True, drop_offset={"x": 0, "y": 0, "z": -4}) + + ctx.delay(minutes=1.5 if bead_mode else 0.5) + + for d in samples: + pick_up(m200) + m200.aspirate(150, d, rate=0.05) + dispose_waste(150, m200) + m200.aspirate(150, d.bottom(z=0.6), rate=0.05) + dispose_waste(150, m200) + m200.drop_tip() if real_mode else m200.return_tip() + ctx.comment("\n") + + # -- MOVE DEEPWELL TO TEMP MOD ON SLOT 1 + ctx.move_labware(labware=deep_plate, new_location=sample_temp_mod, use_gripper=True) + + for i in range(2): + + ctx.comment("\n\n----------Adding Wash Buffer 1----------\n") + for d in samples: + pick_up(m200) + m200.aspirate(200, wash_buffer_1[i], rate=0.05) + m200.dispense(200, d) + m200.mix(15, 200, d, rate=0.3) + slow_tip_withdrawal(m200, d) + ctx.delay(seconds=1) + m200.blow_out(d.top(-3)) + m200.drop_tip() if real_mode else m200.return_tip() + ctx.comment("\n") + + ctx.delay(minutes=5 if real_mode else 0.05) + + # -- MOVE DEEPWELL TO MAG BLOCK + ctx.move_labware(labware=deep_plate, new_location=mag_block, use_gripper=True, drop_offset={"x": 0, "y": 0, "z": -4}) + + ctx.delay(minutes=1.5 if bead_mode else 0.5) + + remove_super(m200, 200, samples, round=i, ethanol=True) + + # -- MOVE DEEPWELL TO DECK + ctx.move_labware(labware=deep_plate, new_location=sample_temp_mod if i == 0 else 5, use_gripper=True) + + if real_mode: + wash_temp_mod.set_temperature(48) + if real_mode: + sample_temp_mod.set_temperature(48) + + # -- MOVE DEEPWELL TO TEMP MOD AT 48 + ctx.move_labware(labware=deep_plate, new_location=sample_temp_mod, use_gripper=True) + + for i in range(3): + + if i == 1 and not real_mode: + continue + + ctx.comment("\n\n----------Adding Wash Buffer 2----------\n") + for d in samples: + pick_up(m200) + m200.aspirate(200, wash_buffer_2[i], rate=0.05) + m200.dispense(200, d) + m200.mix(15, 200, d, rate=0.3) + slow_tip_withdrawal(m200, d) + ctx.delay(seconds=1) + m200.blow_out(d.top(-3)) + m200.drop_tip() if real_mode else m200.return_tip() + ctx.comment("\n") + + ctx.delay(minutes=5 if real_mode else 0.05) + + # -- MOVE DEEPWELL TO MAG BLOCK + ctx.move_labware(labware=deep_plate, new_location=mag_block, use_gripper=True, drop_offset={"x": 0, "y": 0, "z": -4}) + + ctx.delay(minutes=1.5 if bead_mode else 0.5) + + remove_super(m200, 200, samples, round=0 if i <= 1 else 1, ethanol=True) + + # -- MOVE DEEPWELL TO DECK + ctx.move_labware(labware=deep_plate, new_location=sample_temp_mod if i <= 1 else 5, use_gripper=True) + + ctx.comment("\n\n----------Adding Water----------\n") + for d in samples: + pick_up(m50) + m50.aspirate(45, water) + m50.dispense(45, d) + m50.mix(15, 30, d) + slow_tip_withdrawal(m50, d) + ctx.delay(seconds=1) + m50.blow_out(d.top(-3)) + m50.drop_tip() if real_mode else m50.return_tip() + ctx.comment("\n") + + # next step, section 2.4 + + pause( + f"""Water added. Store 22.5ul in freezer. Protocol continues with remaining 22.5ul in armadillo plate in D2. + Load the cold aluminum block with armadillo plate on slot A2, with {2.5*num_col*1.15}ul of ilmn amplification primers in the first column, + and {25*num_col*1.15}ul of equinox library amp mix in column 2 of the cold aluminum block. + """ + ) + + samples = sample_plate.rows()[0][:num_col] + + ctx.comment("\n\n----------Adding Primer----------\n") + add_reagent(2.5, primers, 15, samples, overflow=False, mix=True) + + ctx.comment("\n\n----------Adding Library Amp Mix----------\n") + add_reagent(25, amp_mix, 35, samples, overflow=False, mix=True) + + pause( + f"""Primers and amp mix added. Plate on slot 2 ready for PCR according to section 2.4. + """ + ) + + ########################### SECOND BEAD WASH ############################# + ########################### SECOND BEAD WASH ############################# + ########################### SECOND BEAD WASH ############################# + + ctx.comment("\n\n----------Adding Beads to Sample----------\n") + + adding_beads(90, samples, 100, which_beads=beads2, premix=True, mix_with_beads=True) + + ctx.delay(minutes=5 if real_mode else 0.05) + + # -- MOVE PCR TO MAG BLOCK + ctx.move_labware(labware=sample_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=2 if bead_mode else 0.5) + + ctx.comment("\n\n----------REMOVING SUPER TO TRASH----------\n") + + remove_super(m200, 140, samples, round=3) + + for i in range(2): + ctx.comment("\n\n----------ADDING ETHANOL----------\n") + + ethanol_vol = 150 + pick_up(m200) + for col in samples: + aspirate_ethanol(ethanol_vol, m200) + m200.dispense(ethanol_vol, col.top(), rate=0.4) + ctx.delay(seconds=3) + m200.blow_out() + m200.drop_tip() if real_mode else m200.return_tip() + ctx.delay(30) + + ctx.comment("\n\n----------REMOVING ETHANOL 2----------\n") + remove_super(m200, ethanol_vol, samples, round=i, pcr=True, ethanol=True) + + ctx.delay(minutes=2 if bead_mode else 0.5) + + # -- MOVE PCR PLATE TO TEMP MODULE + ctx.move_labware(labware=sample_plate, new_location=2, use_gripper=True) # back to temp mod + + ctx.comment("\n\n----------ADDING EB (water)----------\n") + adding_eb(32, water, samples, 20) + + ctx.delay(minutes=3 if real_mode else 0.05) + + # -- MOVE PCR PLATE TO MAG BLOCK + ctx.move_labware(labware=sample_plate, new_location=mag_block, use_gripper=True) + + ctx.delay(minutes=2 if bead_mode else 0.5) + + fresh_samples = sample_plate.rows()[0][6 : 6 + num_col] + + ctx.comment("\n\n----------Transferring Sample to PCR Plate----------\n") + for s, d in zip(samples, fresh_samples): + pick_up(m50) + m50.aspirate(32, s.bottom(z=0.6), rate=0.1) + ctx.delay(seconds=2) + m50.aspirate(bead_gap, s.bottom(z=0.4)) + m50.dispense(bead_gap, d.bottom(z=6)) + m50.dispense(32, d) + m50.move_to(d.bottom(z=8)) + ctx.delay(seconds=3) + m50.blow_out() + m50.drop_tip() if real_mode else m50.return_tip() + + samples = fresh_samples + + pause("""Samples are in right half of pcr plate on slot 2""") + + ########################### END SECOND BEAD WASH ############################# + ########################### END SECOND BEAD WASH ############################# + ########################### END SECOND BEAD WASH ############################# + + # Assigning Liquid and colors + strep_beads_liq = ctx.define_liquid( + name="Strep Beads", + description="Beads", + display_color="#7EFF42", + ) + fast_bind_buffer_liq = ctx.define_liquid( + name="Fast Binding Buffer", + description="Fast Binding Buffer", + display_color="#50D5FF", + ) + wash_buffer_1_liq = ctx.define_liquid( + name="Wash Buffer 1", + description="Wash Buffer 1", + display_color="#B925FF", + ) + wash_buffer_2_liq = ctx.define_liquid( + name="Wash Buffer 2", + description="Wash Buffer 2", + display_color="#FF9900", + ) + water_liq = ctx.define_liquid( + name="Water", + description="FE2", + display_color="#0019FF", + ) + beads2_liq = ctx.define_liquid( + name="Purification Beads", + description="FORMAMIDE", + display_color="#007AFF", + ) + ethanol_liq = ctx.define_liquid( + name="Ethanol", + description="BEADS", + display_color="#FF0076", + ) + primers_liq = ctx.define_liquid( + name="Primers", + description="Primers", + display_color="#00FFBC", + ) + amp_mix_liq = ctx.define_liquid( + name="Amp Mix", + description="Amp Mix", + display_color="#00AAFF", + ) + samples_liq = ctx.define_liquid( + name="SAMPLES", + description="SAMPLES", + display_color="#008000", + ) + ethanol_liq = ctx.define_liquid( + name="ETHANOL", + description="ETHANOL", + display_color="#DE3163", + ) + + for column in deep_plate.columns()[:num_col]: + for well in column: + well.load_liquid(liquid=samples_liq, volume=50) + + for column in reag_plate.columns()[1:5]: + for well in column: + well.load_liquid(liquid=fast_bind_buffer_liq, volume=200 * num_col * 1.15) + for well in reag_plate.columns()[0]: + well.load_liquid(liquid=strep_beads_liq, volume=100 * num_col * 1.15) + for column in wash_plate.columns()[:2]: + for well in column: + well.load_liquid(liquid=wash_buffer_1_liq, volume=200 * num_col * 1.15) + for column in wash_plate.columns()[2:5]: + for well in column: + well.load_liquid(liquid=wash_buffer_2_liq, volume=200 * num_col * 1.15) + for well in reag_plate.columns()[5]: + well.load_liquid(liquid=water_liq, volume=100 * num_col * 1.15) + for well in reag_plate.columns()[6]: + well.load_liquid(liquid=beads2_liq, volume=90 * num_col * 1.15) + for well in reag_plate.columns()[7]: + well.load_liquid(liquid=ethanol_liq, volume=300 * num_col) + + for well in temp_reag_plate.columns()[0]: + well.load_liquid(liquid=primers_liq, volume=2.5 * num_col * 1.15) + for well in temp_reag_plate.columns()[1]: + well.load_liquid(liquid=amp_mix_liq, volume=2.5 * num_col * 1.15) + + # for column, column_volume in zip(reag_plate.columns()[2:6], [29, 17.5, 21, 21]): + # for well in column: + # well.load_liquid(liquid=eb_liq, volume=column_volume) + # for well in reag_plate.columns()[6]: + # well.load_liquid(liquid=tet2_liq, volume=17*1.15*num_col) + # for well in reag_plate.columns()[7]: + # well.load_liquid(liquid=fe2_liq, volume=5*1.15*num_col) + # for well in temp_reag_plate.columns()[8]: + # well.load_liquid(liquid=formamide_liq, volume=5*1.15*num_col) + # + # + # for well in reag_res.wells()[1:1+num_ethanol_wells]: + # well.load_liquid(liquid=ethanol_liq, volume=10000) + # + # if num_col <= 2: + # for well in deep_reag_plate.columns()[0]: + # well.load_liquid(liquid=beads_liq, volume=345*1.15*num_col) + # + # else: + # reag_res['A1'].load_liquid(liquid=beads_liq, volume=345*1.15*num_samp) + # + # for well in deep_reag_plate.columns()[1]: + # well.load_liquid(liquid=apobec_liq, volume=80*1.15*num_col) diff --git a/app-testing/http.http b/app-testing/http.http deleted file mode 100644 index 75d697a0b44..00000000000 --- a/app-testing/http.http +++ /dev/null @@ -1,92 +0,0 @@ -@hostname = 192.168.50.89 -@port = 31950 -@host = http://{{hostname}}:{{port}} -@contentType = application/json -@createdAt = {{$datetime iso8601}} -@modifiedBy = {{$processEnv USERNAME}} -@header = opentrons-version: * -@runId = fd0983a4-5d5c-4f88-9925-7ea5422140ee -@binary = true - -GET {{host}}/health -opentrons-version: * - -### -GET {{host}}/modules -opentrons-version: * - -### -GET {{host}}/logs/serial.log -opentrons-version: * -### -GET {{host}}/logs/api.log -opentrons-version: * -### -GET {{host}}/logs/server.log -opentrons-version: * -### -POST {{host}}/robot/lights -opentrons-version: * - -{"on": "{{binary}}" } - - -### -GET {{host}}/runs -opentrons-version: * -Content-Type: {{contentType}} - -### -# Create a run -POST {{host}}/runs -opentrons-version: * -Content-Type: application/json - -{ - "data": {} -} - -### -GET {{host}}/runs/{{runId}}/commands -opentrons-version: * - -### - -POST {{host}}/runs/{{runId}}/commands -opentrons-version: * -Content-Type: application/json - -{ - "data": { - "commandType": "setRailLights", - "params": { - "on": true - } - } -} - - -### - -POST {{host}}/runs/{{runId}}/commands -opentrons-version: * -Content-Type: application/json - -{ - "data": { - "commandType": "loadModule", - "params": { - "model": "magneticModuleV1", - "location": { - "slotName": "3" - } - } - } -} -### -GET {{host}}/commands -opentrons-version: * - -### -GET {{host}}/protocols -opentrons-version: * diff --git a/app-testing/locators.py b/app-testing/locators.py deleted file mode 100644 index 9218cb8f9e4..00000000000 --- a/app-testing/locators.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Interactively test locators in the app. - -pipenv run python -i locators.py -This launches the installed app. -""" - -import importlib -import os - -import src.driver.base -import src.menus.left_menu -import src.pages.app_settings -import src.pages.device_landing -import src.pages.labware_landing -import src.pages.labware_position_check -import src.pages.protocol_landing -import src.resources.ot_robot -import src.resources.robot_data -from conftest import _chrome_options -from dotenv import find_dotenv, load_dotenv -from rich import pretty, traceback -from rich.console import Console -from rich.table import Table -from selenium.webdriver.chrome.webdriver import WebDriver - -# to make printing pretty -console = Console() -pretty.install(console=console) -traceback.install(console=console) - - -def reimport() -> None: - """Reimport so that changes in teh files show up.""" - # tools - importlib.reload(src.driver.base) - importlib.reload(src.resources.ot_robot) - importlib.reload(src.resources.robot_data) - # page objects - importlib.reload(src.menus.left_menu) - importlib.reload(src.pages.device_landing) - importlib.reload(src.pages.app_settings) - importlib.reload(src.pages.protocol_landing) - importlib.reload(src.pages.labware_position_check) - - -# variables -base = None -kansas = None -dev = None -emulated_alpha = None -device_landing = None -app_settings = None -left_menu = None -protocol_landing = None -labware_landing = None -labware_position_check = None -# These variables should reflect the variables used in tests so steps may ba copy pasta. -variables = [ - "base", - "kansas", - "dev", - "emulated_alpha", - "device_landing", - "left_menu", - "app_settings", - "protocol_landing", - "labware_landing", - "labware_position_check", -] - - -def instantiate(driver: WebDriver, console: Console) -> None: - """Tie the imported or reimported packages to variables.""" - global base - base = src.driver.base.Base(driver, console, "REPL") - global kansas - kansas = src.resources.ot_robot.OtRobot(console, src.resources.robot_data.Kansas()) - global dev - dev = src.resources.ot_robot.OtRobot(console, src.resources.robot_data.Dev()) - global emulated_alpha - emulated_alpha = src.resources.ot_robot.OtRobot(console, src.resources.robot_data.EmulatedAlpha()) - global device_landing - device_landing = src.pages.device_landing.DeviceLanding(driver, console, "REPL") - global left_menu - left_menu = src.menus.left_menu.LeftMenu(driver, console, "REPL") - global app_settings - app_settings = src.pages.app_settings.AppSettings(driver, console, "REPL") - global protocol_landing - protocol_landing = src.pages.protocol_landing.ProtocolLanding(driver, console, "REPL") - global labware_landing - labware_landing = src.pages.labware_landing.LabwareLanding(driver, console, "REPL") - global labware_position_check - labware_position_check = src.pages.labware_position_check.LabwarePositionCheck(driver, console, "REPL") - - -# Check to see if we have a dotenv file and use it -if find_dotenv(): - load_dotenv(find_dotenv()) -# use env variable to prevent the analytics pop up -os.environ["OT_APP_ANALYTICS__SEEN_OPT_IN"] = "true" -# app should look on localhost for robots -# currently broken -# os.environ["OT_APP_DISCOVERY__CANDIDATES"] = '["localhost"]' -# dev tools open at start -os.environ["OT_APP_DEVTOOLS"] = "true" -driver: WebDriver = WebDriver(options=_chrome_options()) - -# instantiate the variables for easy use of our -# page objects and resources in the REPL -instantiate(driver, console) - - -# print the list ov variables in a table -table = Table(title="Instantiated Holders") -table.add_column("variable name", justify="left", style="cyan", no_wrap=True) -for h in variables: - table.add_row(h) -console.print(table, style="white on blue") - - -def reload() -> None: - """Run when you update a file.""" - reimport() - instantiate(driver, console) - console.print(table, style="white on blue") - - -def clean_exit() -> None: - """Run to exit chromedriver and the REPL cleanly. - - If you do not use this method orphan chromedriver and app instances might be left open - pkill -x chromedriver - If you do forget to use it. - """ - # Close the app/chromedriver - driver.quit() - # Exit the REPL - exit() diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json index c7675dbb2f3..8508c787399 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json @@ -8846,6 +8846,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json index 1c99a464e91..3a7afafd988 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json @@ -2314,6 +2314,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" @@ -2325,6 +2326,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p20_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json index 229113e4262..a0c54738832 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json @@ -2350,6 +2350,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" @@ -2361,6 +2362,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p20_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json index e0f9232bc89..7e7addf373f 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -2350,6 +2350,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" @@ -2361,6 +2362,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p20_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10d250f82a][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10d250f82a][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction].json index a484ba46e4f..5fea9104c3d 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10d250f82a][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10d250f82a][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtraction].json @@ -13208,6 +13208,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[12a2a22254][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[12a2a22254][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment].json index 98eff536f02..f5881d2e4b2 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[12a2a22254][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[12a2a22254][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichment].json @@ -9416,6 +9416,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_multi_flex", "tipOverlapNotAfterVersion": "v0" @@ -9427,6 +9428,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p50_multi_flex", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[1960aa7a4c][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[1960aa7a4c][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4].json index a980003879c..f607344fbdc 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[1960aa7a4c][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[1960aa7a4c][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAEnrichmentv4].json @@ -11683,6 +11683,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_multi_flex", "tipOverlapNotAfterVersion": "v0" @@ -11694,6 +11695,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p50_multi_flex", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[19ffa9c839][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin2].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[19ffa9c839][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin2].json index f00e7b715aa..63cf62d060c 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[19ffa9c839][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin2].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[19ffa9c839][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin2].json @@ -479,7 +479,7 @@ "errorInfo": { "args": "('trash bin in slot 12 prevents heaterShakerModuleV1 from using slot 9.',)", "class": "DeckConflictError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin2.py\", line 11, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 826, in load_module\n module_core = self._core.load_module(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/protocol.py\", line 432, in load_module\n deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/deck_conflict.py\", line 203, in check\n wrapped_deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/motion_planning/deck_conflict.py\", line 210, in check\n raise DeckConflictError(\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin2.py\", line 11, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 827, in load_module\n module_core = self._core.load_module(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/protocol.py\", line 432, in load_module\n deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/deck_conflict.py\", line 203, in check\n wrapped_deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/motion_planning/deck_conflict.py\", line 210, in check\n raise DeckConflictError(\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[2b7fcb5b23][Flex_S_v2_18_P1000_96_TipTrackingBug].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[2b7fcb5b23][Flex_S_v2_18_P1000_96_TipTrackingBug].json index b54a3e3c161..23ea991b630 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[2b7fcb5b23][Flex_S_v2_18_P1000_96_TipTrackingBug].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[2b7fcb5b23][Flex_S_v2_18_P1000_96_TipTrackingBug].json @@ -2315,6 +2315,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[2eaf98de6a][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_TriggerPrepareForMountMovement].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[2eaf98de6a][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_TriggerPrepareForMountMovement].json index 053ab4815ba..57696ce475f 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[2eaf98de6a][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_TriggerPrepareForMountMovement].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[2eaf98de6a][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_TriggerPrepareForMountMovement].json @@ -9364,6 +9364,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4a82419f1f][OT2_S_v2_16_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4a82419f1f][OT2_S_v2_16_P300M_P20S_HS_TC_TM_SmokeTestV3].json index 0d134da939f..c254bff2b6c 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4a82419f1f][OT2_S_v2_16_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4a82419f1f][OT2_S_v2_16_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -2350,6 +2350,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" @@ -2361,6 +2362,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p20_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b17883f74][OT2_S_v2_17_P300M_P20S_HS_TC_TM_dispense_changes].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b17883f74][OT2_S_v2_17_P300M_P20S_HS_TC_TM_dispense_changes].json index cc14940b50f..b4135329fe5 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b17883f74][OT2_S_v2_17_P300M_P20S_HS_TC_TM_dispense_changes].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b17883f74][OT2_S_v2_17_P300M_P20S_HS_TC_TM_dispense_changes].json @@ -2350,6 +2350,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" @@ -2361,6 +2362,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p20_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json index 328793db623..5c9ca5c9fb4 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json @@ -1518,6 +1518,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json index 61f388b826f..c1432ae4f86 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json @@ -29,7 +29,7 @@ "errorInfo": { "args": "('Staging areas not permitted for trash bin.',)", "class": "ValueError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4.py\", line 15, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 524, in load_trash_bin\n raise ValueError(\"Staging areas not permitted for trash bin.\")\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4.py\", line 15, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 525, in load_trash_bin\n raise ValueError(\"Staging areas not permitted for trash bin.\")\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json index c8f61802e20..f06ec769e8a 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json @@ -1166,6 +1166,7 @@ }, "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json index c138954002b..5ce30269e72 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json @@ -29,7 +29,7 @@ "errorInfo": { "args": "('Cannot load a module onto a staging slot.',)", "class": "ValueError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4.py\", line 15, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 824, in load_module\n raise ValueError(\"Cannot load a module onto a staging slot.\")\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4.py\", line 15, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 825, in load_module\n raise ValueError(\"Cannot load a module onto a staging slot.\")\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json index 85391bfc5b4..f65ba0ec4a6 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json @@ -13127,6 +13127,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json index b842bd6f929..6204e401af3 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json @@ -479,7 +479,7 @@ "errorInfo": { "args": "('trash bin in slot 12 prevents heaterShakerModuleV1 from using slot 11.',)", "class": "DeckConflictError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1.py\", line 11, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 826, in load_module\n module_core = self._core.load_module(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/protocol.py\", line 432, in load_module\n deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/deck_conflict.py\", line 203, in check\n wrapped_deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/motion_planning/deck_conflict.py\", line 223, in check\n raise DeckConflictError(\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1.py\", line 11, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 827, in load_module\n module_core = self._core.load_module(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/protocol.py\", line 432, in load_module\n deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/deck_conflict.py\", line 203, in check\n wrapped_deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/motion_planning/deck_conflict.py\", line 223, in check\n raise DeckConflictError(\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json index c419d6bca3a..52e66a33e73 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json @@ -2314,6 +2314,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" @@ -2325,6 +2326,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p20_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json index c99be9b39e0..cfc116cbeeb 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json @@ -29,7 +29,7 @@ "errorInfo": { "args": "('Invalid location for trash bin: C2.\\nValid slots: Any slot in column 1 or 3.',)", "class": "InvalidTrashBinLocationError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2.py\", line 15, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 525, in load_trash_bin\n addressable_area_name = validation.ensure_and_convert_trash_bin_location(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/validation.py\", line 333, in ensure_and_convert_trash_bin_location\n raise InvalidTrashBinLocationError(\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2.py\", line 15, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 526, in load_trash_bin\n addressable_area_name = validation.ensure_and_convert_trash_bin_location(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/validation.py\", line 333, in ensure_and_convert_trash_bin_location\n raise InvalidTrashBinLocationError(\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json index 9fb8c3fb8ed..fea36d374be 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -2350,6 +2350,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" @@ -2361,6 +2362,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p20_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88a20da279][Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88a20da279][Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2].json index 8ead63aab5b..4aae462df09 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88a20da279][Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88a20da279][Flex_S_v2_15_P50M_P1000M_KAPALibraryQuantLongv2].json @@ -17856,6 +17856,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p50_multi_flex", "tipOverlapNotAfterVersion": "v0" @@ -17867,6 +17868,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p1000_multi_flex", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json index 52fc02e1752..3383e95d155 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json @@ -1163,6 +1163,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json index b7cb92b3474..ef540932aed 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json @@ -1163,6 +1163,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json index 7c9055c26e7..013ba9e84cd 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json @@ -2681,7 +2681,7 @@ "errorInfo": { "args": "('thermocyclerModuleV2 in slot 7 prevents thermocyclerModuleV1 from using slot 7.',)", "class": "DeckConflictError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError.py\", line 19, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 826, in load_module\n module_core = self._core.load_module(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/legacy/legacy_protocol_core.py\", line 333, in load_module\n self._deck_layout[resolved_location] = geometry\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/legacy/deck.py\", line 186, in __setitem__\n deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/motion_planning/deck_conflict.py\", line 210, in check\n raise DeckConflictError(\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError.py\", line 19, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 827, in load_module\n module_core = self._core.load_module(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/legacy/legacy_protocol_core.py\", line 333, in load_module\n self._deck_layout[resolved_location] = geometry\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/legacy/deck.py\", line 186, in __setitem__\n deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/motion_planning/deck_conflict.py\", line 210, in check\n raise DeckConflictError(\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json index 16033490603..11240d24818 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json @@ -1224,6 +1224,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json index 3574dbf9880..40e5ad3a060 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json @@ -7073,6 +7073,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json index 7031606682f..d59cd4468ae 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json @@ -29,7 +29,7 @@ "errorInfo": { "args": "('Fixed Trash is not supported on Flex protocols in API Version 2.16 and above.',)", "class": "APIVersionError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp.py\", line 15, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 1130, in fixed_trash\n raise APIVersionError(\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp.py\", line 15, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 1148, in fixed_trash\n raise APIVersionError(\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json index 1cb2ff42eb0..74e89fc8a8b 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json @@ -10968,6 +10968,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json index 754cc70bc2a..adb6b79d45c 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json @@ -2376,6 +2376,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json index 03cac8f1396..10221b74afc 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json @@ -1163,6 +1163,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json index 22faaf1ac50..a2ea9418afb 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json @@ -10685,6 +10685,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p1000_single_flex", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json index 558c54d2c3c..74c484bf785 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json @@ -9377,6 +9377,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json index e1faf5af6d2..69cb4ee2e2a 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json @@ -6968,7 +6968,7 @@ "errorInfo": { "args": "('Cannot aspirate more than pipette max volume',)", "class": "AssertionError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_runner/task_queue.py\", line 84, in _run\n await self._run_func()\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_runner/task_queue.py\", line 61, in _do_run\n await func(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_runner/protocol_runner.py\", line 235, in run_func\n await self._protocol_executor.execute(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_runner/python_protocol_wrappers.py\", line 156, in execute\n await to_thread.run_sync(\n\n File \"/usr/local/lib/python3.10/site-packages/anyio/to_thread.py\", line 33, in run_sync\n return await get_asynclib().run_sync_in_worker_thread(\n\n File \"/usr/local/lib/python3.10/site-packages/anyio/_backends/_asyncio.py\", line 877, in run_sync_in_worker_thread\n return await future\n\n File \"/usr/local/lib/python3.10/site-packages/anyio/_backends/_asyncio.py\", line 807, in run\n result = context.run(func, *args)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute.py\", line 58, in run_protocol\n execute_json_v4.dispatch_json(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_json_v4.py\", line 272, in dispatch_json\n pipette_command_map[command_type]( # type: ignore\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_json_v3.py\", line 159, in _aspirate\n pipette.aspirate(volume, location)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line 272, in aspirate\n self._core.aspirate(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py\", line 119, in aspirate\n new_volume <= self._pipette_dict[\"working_volume\"]\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_runner/task_queue.py\", line 84, in _run\n await self._run_func()\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_runner/task_queue.py\", line 61, in _do_run\n await func(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_runner/protocol_runner.py\", line 235, in run_func\n await self._protocol_executor.execute(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_runner/python_protocol_wrappers.py\", line 156, in execute\n await to_thread.run_sync(\n\n File \"/usr/local/lib/python3.10/site-packages/anyio/to_thread.py\", line 33, in run_sync\n return await get_asynclib().run_sync_in_worker_thread(\n\n File \"/usr/local/lib/python3.10/site-packages/anyio/_backends/_asyncio.py\", line 877, in run_sync_in_worker_thread\n return await future\n\n File \"/usr/local/lib/python3.10/site-packages/anyio/_backends/_asyncio.py\", line 807, in run\n result = context.run(func, *args)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute.py\", line 58, in run_protocol\n execute_json_v4.dispatch_json(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_json_v4.py\", line 272, in dispatch_json\n pipette_command_map[command_type]( # type: ignore\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_json_v3.py\", line 159, in _aspirate\n pipette.aspirate(volume, location)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line 272, in aspirate\n self._core.aspirate(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py\", line 119, in aspirate\n new_volume <= self._pipette_dict[\"working_volume\"]\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json index b8a4d646453..c1dfa9154f5 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -2350,6 +2350,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" @@ -2361,6 +2362,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p20_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json index 01e8710b2a5..5a985ec4577 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json @@ -1163,6 +1163,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" @@ -3514,7 +3515,7 @@ "errorInfo": { "args": "('Nozzle layout configuration of style SINGLE is currently unsupported.',)", "class": "ValueError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle.py\", line 16, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line 2006, in configure_nozzle_layout\n raise ValueError(\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle.py\", line 16, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line 2006, in configure_nozzle_layout\n raise ValueError(\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json index 2d08dffb83a..f270bdabcec 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json @@ -138,7 +138,7 @@ "errorInfo": { "args": "('thermocyclerModuleV2 in slot B1 prevents trash bin from using slot A1.',)", "class": "DeckConflictError", - "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict.py\", line 13, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 383, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 530, in load_trash_bin\n trash_bin = self._core.load_trash_bin(slot_name, addressable_area_name)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/protocol.py\", line 546, in load_trash_bin\n self._add_disposal_location_to_engine(trash_bin)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/protocol.py\", line 149, in _add_disposal_location_to_engine\n deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/deck_conflict.py\", line 203, in check\n wrapped_deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/motion_planning/deck_conflict.py\", line 210, in check\n raise DeckConflictError(\n" + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line 147, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line 1, in \n\n File \"Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict.py\", line 13, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line 392, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/protocol_context.py\", line 531, in load_trash_bin\n trash_bin = self._core.load_trash_bin(slot_name, addressable_area_name)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/protocol.py\", line 550, in load_trash_bin\n self._add_disposal_location_to_engine(trash_bin)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/protocol.py\", line 149, in _add_disposal_location_to_engine\n deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/core/engine/deck_conflict.py\", line 203, in check\n wrapped_deck_conflict.check(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/motion_planning/deck_conflict.py\", line 210, in check\n raise DeckConflictError(\n" }, "errorType": "PythonException", "isDefined": false, diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de842b7217][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de842b7217][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction].json index 155cfb72e1f..1509cf47b92 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de842b7217][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de842b7217][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_OmegaHDQDNAExtraction].json @@ -13332,6 +13332,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e907467039][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e907467039][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x].json index 9fcb7e3a3f4..13b3dab1772 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e907467039][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e907467039][Flex_S_v2_15_P1000M_P50M_GRIP_HS_MB_TC_TM_IlluminaDNAPrep24x].json @@ -8146,6 +8146,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p1000_multi_flex", "tipOverlapNotAfterVersion": "v0" @@ -8157,6 +8158,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p50_multi_flex", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json index 398ee7988aa..431f6a0bbfc 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json @@ -1311,6 +1311,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json index 931d12c29bb..8f8e6fbdd83 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json @@ -8179,6 +8179,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json index e680b9d7647..6c64aa454fe 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json @@ -1272,6 +1272,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json index e0514be556f..9dacbc16574 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json @@ -11150,6 +11150,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p1000_96", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json index 1633e926382..e2b60318f13 100644 --- a/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json +++ b/app-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json @@ -2350,6 +2350,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "left", "pipetteName": "p300_multi_gen2", "tipOverlapNotAfterVersion": "v0" @@ -2361,6 +2362,7 @@ "commandType": "loadPipette", "notes": [], "params": { + "liquidPresenceDetection": false, "mount": "right", "pipetteName": "p20_single_gen2", "tipOverlapNotAfterVersion": "v0" diff --git a/app-testing/tests/app_settings_test.py b/app-testing/tests/app_settings_test.py deleted file mode 100644 index 0b0d49f4c38..00000000000 --- a/app-testing/tests/app_settings_test.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Test the initial state the application with various setups.""" - -import pytest -from automation.menus.left_menu import LeftMenu -from automation.pages.app_settings import AppSettings -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver - - -@pytest.mark.skip("Need to fix.") -def test_app_settings( - driver: WebDriver, - console: Console, - request: pytest.FixtureRequest, -) -> None: - """Validate most of the app settings are present and that some may be toggled or selected.""" - # Instantiate the page object for the App settings. - app_settings: AppSettings = AppSettings(driver, console, request.node.nodeid) - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - - # General tab verification - left_menu.navigate("app-settings") - assert app_settings.get_app_settings_header().text == "App Settings" - - assert app_settings.get_connect_robot_via_IP_header().is_displayed() - assert app_settings.get_connect_to_robot_via_IP_address_button().is_displayed() - app_settings.click_connect_to_robot_via_IP_address_button() - assert app_settings.get_connect_to_robot_via_IP_address_slideout_header().is_displayed() - assert app_settings.get_link_learn_more_about_connecting_a_robot_manually().is_displayed() - assert ( - app_settings.get_link_learn_more_about_connecting_a_robot_manually().get_attribute("href") - == "https://support.opentrons.com/s/article/Manually-adding-a-robot-s-IP-address" - ) - assert app_settings.get_textbox_to_enter_the_ip().is_displayed() - app_settings.click_add_ip_or_hostname() - assert app_settings.get_try_again_link().is_displayed() - app_settings.enter_hostname("localhost") - assert app_settings.get_add_button().is_displayed() - app_settings.click_add_button() - assert app_settings.get_done_button().is_displayed() - app_settings.click_done_button() - - assert app_settings.get_app_software_version_text().text == "App Software Version" - assert ( - app_settings.get_release_notes_link().get_attribute("href") - == "https://github.com/Opentrons/opentrons/blob/edge/app-shell/build/release-notes.md" - ) - assert app_settings.get_app_software_version_value().is_displayed() - - assert app_settings.get_link_restore_previous_version().is_displayed() - app_settings.click_link_restore_previous_version() - assert app_settings.get_how_to_restore_software_version_modal().is_displayed() - assert app_settings.get_learn_more_about_uninstalling_opentrons_app().is_displayed() - assert ( - app_settings.get_learn_more_about_uninstalling_opentrons_app().get_attribute("href") - == "https://support.opentrons.com/s/article/Uninstall-the-Opentrons-App" - ) - assert app_settings.get_link_to_previous_releases().is_displayed() - assert app_settings.get_link_to_previous_releases().get_attribute("href") == "https://github.com/Opentrons/opentrons/releases" - app_settings.click_close_previous_software_modal() - - assert app_settings.get_link_app_robot_sync().is_displayed() - assert ( - app_settings.get_link_app_robot_sync().get_attribute("href") - == "https://support.opentrons.com/s/article/Get-started-Update-your-OT-2" - ) - assert app_settings.get_software_update_alert_header().is_displayed() - assert app_settings.get_software_update_alert_toggle().is_displayed() - - # can't do this, it makes the alert appear - # app_settings.click_software_update_alert_toggle() - - # Privacy Tab verification - app_settings.click_privacy_tab() - assert app_settings.get_robot_app_analytics().is_displayed() - assert app_settings.get_robot_app_analytics_toggle().is_displayed() - # app_settings.click_robot_app_analytics() - - # Advanced Tab Verification - app_settings.click_advanced_tab() - assert app_settings.get_advanced_tab().is_displayed() - assert app_settings.get_update_channel().is_displayed() - assert app_settings.get_update_channel_latest_stable().get_attribute("value") == "alpha" - assert app_settings.get_additional_custom_labware_source_folder().is_displayed() - assert app_settings.get_change_labware_source_folder_button().is_displayed() - assert app_settings.get_additional_source_folder().is_displayed() - - assert app_settings.get_tip_length_calibration_method().is_displayed() - assert app_settings.get_tip_calibration_block_to_calibrate().is_displayed() - app_settings.click_tip_calibration_block_to_calibrate() - assert app_settings.get_tip_calibration_trash_bin().is_displayed() - app_settings.click_tip_calibration_trash_bin() - assert app_settings.get_tip_calibration_prompt_choose().is_displayed() - app_settings.click_tip_calibration_prompt_choose() - - assert app_settings.get_display_unavailable_robots_header().is_displayed() - assert app_settings.get_display_unavailable_robots_toggle().is_displayed() - app_settings.click_unavailable_robot_toggle() - - assert app_settings.get_clear_unavailable_robots_header().is_displayed() - assert app_settings.get_clear_unavailable_robots_list_button().is_displayed() - app_settings.click_clear_unavailable_robot_button() - - assert app_settings.get_enable_developer_tool_header().is_displayed() - assert app_settings.get_enable_developer_tools_toggle().is_displayed() - # not available when we run the app through chromedriver like this - # app_settings.click_enable_developer_tools_toggle() - # app_settings.click_feature_flag_tab() - # assert app_settings.get_feature_flag_tab().is_displayed() diff --git a/app-testing/tests/calibrate_test.py b/app-testing/tests/calibrate_test.py deleted file mode 100644 index 52bac253b93..00000000000 --- a/app-testing/tests/calibrate_test.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Test the initial state the application with various setups.""" - -import time -from typing import List - -import pytest -from automation.menus.left_menu import LeftMenu -from automation.pages.deck_calibrate import DeckCalibration -from automation.pages.device_landing import DeviceLanding -from automation.resources.ot_robot import OtRobot -from automation.resources.robot_data import EmulatedAlpha, RobotDataType -from pytest import FixtureRequest -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver - - -@pytest.mark.skip("Need to fix.") -def test_deck_calibrate( - driver: WebDriver, - console: Console, - robots: List[RobotDataType], - request: FixtureRequest, -) -> None: - """Deck Calibrate.""" - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - device_landing: DeviceLanding = DeviceLanding(driver, console, request.node.nodeid) - left_menu.navigate("devices") - # this test is against only the EmulatedAlpha robot - robot = next(robot for robot in robots if robot.name == EmulatedAlpha.name) - ot_robot = OtRobot(console, robot) - console.print(f"Testing against robot {ot_robot.data.display_name}", style="white on blue") - assert ot_robot.is_alive(), "is the robot available?" - - # calibrate - - device_landing.click_overflow_menu_button_on_device_landing(ot_robot.data.display_name) - device_landing.click_overflow_robot_settings(ot_robot.data.display_name) - - # Now we are on Robot Settings > calibration tab - calibrate = DeckCalibration(driver, console, request.node.nodeid) - - if not device_landing.is_deck_calibrated(): - console.print("Calibrating deck.", style="bold blue") - # open calibration again - device_landing.open_calibration() - calibrate.calibrate_deck() - else: - console.print("Deck is calibrated.", style="bold blue") - - -def calibrate_pipette(device_landing: DeviceLanding) -> None: - """Do the steps of calibration for a pipette.""" - device_landing.click_continue_with_calibration_block() - device_landing.click_start_tip_length() - device_landing.click_confirm_placement() - device_landing.click_down() - device_landing.click_down() - device_landing.click_down() - device_landing.click_save_nozzle_z() - device_landing.click_back() - device_landing.click_back() - device_landing.click_pickup_tip() - device_landing.click_yes_move_to_measure() - device_landing.shift_down_arrow_key() - device_landing.shift_down_arrow_key() - device_landing.shift_down_arrow_key() - device_landing.shift_down_arrow_key() - device_landing.click_save_the_tip_length() - device_landing.click_continue_to_pipette_offset() - device_landing.shift_down_arrow_key() - device_landing.shift_down_arrow_key() - device_landing.shift_down_arrow_key() - device_landing.shift_down_arrow_key() - device_landing.shift_down_arrow_key() - device_landing.shift_down_arrow_key() - device_landing.click_save_calibration_move_to_slot_1() - device_landing.up_arrow_key() - device_landing.up_arrow_key() - device_landing.click_save_calibration() - device_landing.click_return_tip_exit() - - -def test_calibrate_pipettes( - driver: WebDriver, - console: Console, - robots: List[RobotDataType], - request: FixtureRequest, -) -> None: - """Deck Calibrate the dev robot.""" - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - device_landing: DeviceLanding = DeviceLanding(driver, console, request.node.nodeid) - - # this test is against only the EmulatedAlpha robot - robot = next(robot for robot in robots if robot.name == EmulatedAlpha.name) - ot_robot: OtRobot = OtRobot(console, robot) - console.print(f"Testing against robot {ot_robot.data.display_name}", style="white on blue") - assert ot_robot.is_alive(), "is the robot available?" - - assert ot_robot.deck_calibrated() is True, "Stopping test, deck must be calibrated to calibrate pipettes." - - # calibrate the left pipette - # devices > robot detail - - left_menu.navigate("devices") - device_landing.click_robot_banner(ot_robot.data.display_name) - # click calibrate now banner - # left should be the first in the DOM tried to be exact with locators but had issues on Windows. - device_landing.click_calibrate_now() - calibrate_pipette(device_landing) - - # done calibrating left pipette now do right - # devices > robot detail - time.sleep(6) # when spinner up, click will error # todo wait for spinner gone - left_menu.navigate("devices") # when spinner up, click will error - device_landing.click_robot_banner(ot_robot.data.display_name) - device_landing.click_calibrate_now() - calibrate_pipette(device_landing) - - time.sleep(6) # when spinner up, click will error # todo wait for spinner gone - left_menu.navigate("devices") - device_landing.click_overflow_menu_button_on_device_landing(ot_robot.data.display_name) - device_landing.click_overflow_robot_settings(ot_robot.data.display_name) - # Now we are on Robot Settings > calibration tab - banner = device_landing.invisible_pipette_offset_missing_banner_safely() - # This is the last calibration to run so the banner should go away - # pipettes and tip lengths for both - assert banner is None - assert ot_robot.pipettes_calibrated() - assert ot_robot.tip_length_calibrated() - - -def test_all_calibrated_api( - console: Console, - robots: List[RobotDataType], -) -> None: - """Test that all calibrations are valid at API level.""" - # this test is against only the EmulatedAlpha robot - robot = next(robot for robot in robots if robot.name == EmulatedAlpha.name) - ot_robot: OtRobot = OtRobot(console, robot) - console.print(f"Testing against robot {ot_robot.data.name}", style="white on blue") - assert ot_robot.is_alive(), "is the robot available?" - assert ot_robot.deck_calibrated() is True, "Stopping test, deck must be calibrated to calibrate pipettes." - assert ot_robot.pipettes_calibrated() - assert ot_robot.tip_length_calibrated() diff --git a/app-testing/tests/device_landing_test.py b/app-testing/tests/device_landing_test.py deleted file mode 100644 index 5b77f32e5d0..00000000000 --- a/app-testing/tests/device_landing_test.py +++ /dev/null @@ -1,225 +0,0 @@ -"""Test the Device Landing Page of Unified App.""" - -import time -from pathlib import Path -from typing import Dict, List - -import pytest -from automation.driver.drag_drop import drag_and_drop_file -from automation.menus.left_menu import LeftMenu -from automation.pages.device_landing import DeviceLanding -from automation.pages.labware_setup import LabwareSetup -from automation.pages.modal import Modal -from automation.pages.module_setup import ModuleSetup -from automation.pages.protocol_landing import ProtocolLanding -from automation.pages.setup_calibration import SetupCalibration -from automation.resources.ot_robot import OtRobot -from automation.resources.robot_data import Dev, EmulatedAlpha, RobotDataType -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver - - -@pytest.mark.skip("Need to fix.") -def test_device_landing( - driver: WebDriver, - console: Console, - robots: List[RobotDataType], - request: pytest.FixtureRequest, -) -> None: - """Test some of the functionality and data displayed on the device landing page.""" - modal: Modal = Modal(driver, console, request.node.nodeid) - not_now_button = modal.get_not_now() - if not_now_button is not None: - modal.base.click(modal.not_now) - - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - left_menu.navigate("devices") - - device_landing: DeviceLanding = DeviceLanding(driver, console, request.node.nodeid) - assert device_landing.get_device_header().is_displayed() - assert device_landing.get_how_to_setup_a_robot().is_displayed() - device_landing.click_how_to_setup_a_robot() - assert device_landing.get_setup_a_robot_header().is_displayed() - assert device_landing.get_link_to_setting_up_a_new_robot().is_displayed() - device_landing.click_close_button() - # just testing on the emulated_alpha robot - robot = next(robot for robot in robots if robot.name == EmulatedAlpha.name) - ot_robot = OtRobot(console, robot) - - if ot_robot.is_alive(): - console.print(f"Testing against robot {ot_robot.data.name}", style="white on blue") - else: - console.print(f"Robot {ot_robot.data.display_name} not alive.", style="white on blue") - raise AssertionError("Fail Fast robot not alive.") - - # Is the robot connected? - device_landing.robot_banner(robot_name=ot_robot.data.display_name) - assert device_landing.get_robot_image(robot_name=ot_robot.data.display_name).is_displayed() - assert device_landing.get_left_mount_pipette(robot_name=ot_robot.data.display_name).is_displayed() - assert device_landing.get_right_mount_pipette(robot_name=ot_robot.data.display_name).is_displayed() - assert device_landing.get_overflow_button_on_device_landing(ot_robot.data.display_name).is_displayed() - # go to the detail page - device_landing.get_robot_image(robot_name=ot_robot.data.display_name).click() - assert device_landing.get_image_robot_overview().is_displayed() - assert device_landing.get_robot_name_device_detail(robot_name=ot_robot.data.display_name).is_displayed() - assert device_landing.get_pipettes_and_modules_header_text() == "Pipettes and Modules" - assert device_landing.get_recent_protocol_runs_header_text() == f"{ot_robot.data.display_name}'s Protocol Runs" - assert device_landing.set_lights(True) is True, "Lights toggle was not set to on." - - assert device_landing.get_left_mount_pipette_device_detail(ot_robot.data.left_pipette_model).is_displayed() - assert device_landing.get_right_mount_pipette_device_detail(ot_robot.data.right_pipette_model).is_displayed() - # assert device_landing.get_mag_deck_image().is_displayed() - # assert device_landing.get_mag_module_name().is_displayed() - assert device_landing.get_thermocycler_gen2_deck_image().is_displayed() - assert device_landing.get_thermocycler_gen2_module_name().is_displayed() - assert device_landing.get_heater_shaker_deck_image().is_displayed() - assert device_landing.get_heater_shaker_module_name().is_displayed() - # assert device_landing.get_tem_deck_image().is_displayed() - # assert device_landing.get_tem_module_name().is_displayed() - - -@pytest.mark.skip("Need to fix.") -def test_run_protocol_robot_landing_page( - driver: WebDriver, - console: Console, - test_protocols: Dict[str, Path], - robots: List[RobotDataType], - request: pytest.FixtureRequest, -) -> None: - """Run a protocol from the device landing page. - - Must have all calibrations done for this to run. - """ - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - left_menu.navigate("protocols") - protocol_landing = ProtocolLanding(driver, console, request.node.nodeid) - console.log(f"uploading protocol: {test_protocols['protocoluploadjson'].resolve()}") - drag_and_drop_file( - protocol_landing.get_drag_drop_file_button(), - test_protocols["protocoluploadjson"], - ) - # todo dynamic - # waiting for protocol to analyze - time.sleep(3) - device_landing: DeviceLanding = DeviceLanding(driver, console, request.node.nodeid) - left_menu.navigate("devices") - # this test is against only the emulated alpha robot - robot = next(robot for robot in robots if robot.name == EmulatedAlpha.name) - ot_robot = OtRobot(console, robot) - console.print(f"Testing against robot {ot_robot.data.name}", style="white on blue") - assert ot_robot.is_alive(), "is the robot available?" - if device_landing.get_robot_banner_safe(ot_robot.data.display_name) is None: - raise AssertionError(f"Stopping the test, the robot with name {ot_robot.data.display_name} is not found.") - if device_landing.get_go_to_run_safe(ot_robot.data.display_name) is not None: - raise AssertionError(f"Stopping the test, the robot with name {ot_robot.data.display_name} has an active run.") - device_landing.click_overflow_menu_button_on_device_landing(ot_robot.data.display_name) - device_landing.click_run_a_protocol_on_overflow(ot_robot.data.display_name) - assert device_landing.get_protocol_name_device_detail_slideout().is_displayed() - # see that the overflow menu has disappeared (fixed bug) - assert device_landing.get_run_a_protocol_on_overflow(ot_robot.data.display_name) is None - device_landing.click_proceed_to_setup_button_device_landing_page() - time.sleep(5) - - # Verify the Setup for run page - setup_calibrate = SetupCalibration(driver, console, request.node.nodeid) - assert setup_calibrate.get_robot_calibration().text == "Robot Calibration" - setup_calibrate.click_robot_calibration() - assert setup_calibrate.get_deck_calibration().text == "Deck Calibration" - assert setup_calibrate.get_required_pipettes().text == "Required Pipettes" - assert setup_calibrate.get_calibration_ready_locator().text == "Calibration Ready" - assert setup_calibrate.get_required_tip_length_calibration().text == "Required Tip Length Calibrations" - module_setup = ModuleSetup(driver, console, request.node.nodeid) - assert module_setup.get_proceed_to_module_setup().is_displayed() - module_setup.click_proceed_to_module_setup() - assert module_setup.get_module_setup_text_locator().text == "Module Setup" - assert module_setup.get_thermocycler_module().text == "Thermocycler Module GEN1" - assert module_setup.get_magnetic_module().text == "Magnetic Module GEN2" - assert module_setup.get_temperature_module().text == "Temperature Module GEN2" - assert module_setup.get_proceed_to_labware_setup().is_displayed() - module_setup.click_proceed_to_labware_setup() - labware_setup = LabwareSetup(driver, console, request.node.nodeid) - assert labware_setup.get_labware_setup_text().is_displayed() - labware_setup.click_proceed_to_run_button() - device_landing.click_start_run_button() - assert device_landing.get_run_button().is_displayed() - assert device_landing.get_success_banner_run_page().is_displayed() - - # TC2 : Running the protocol from run page by clicking on Run again button - device_landing.click_start_run_button() - assert device_landing.get_run_button().is_displayed() - device_landing.click_start_run_button() # clicking on start run after clicking run again on Run page - assert device_landing.get_run_button().is_displayed() - assert device_landing.get_success_banner_run_page().is_displayed() - - # Uncurrent the run from the robot - assert protocol_landing.get_close_button_uncurrent_run().is_displayed() - protocol_landing.click_close_button_uncurrent_run() - - -@pytest.mark.skip("Need to fix.") -def test_run_protocol_robot_detail_page( - driver: WebDriver, - console: Console, - test_protocols: Dict[str, Path], - robots: List[RobotDataType], - request: pytest.FixtureRequest, -) -> None: - """Test creating and running from the device detail page. - - Must have all calibrations done for this to run. - """ - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - left_menu.navigate("protocols") - protocol_landing = ProtocolLanding(driver, console, request.node.nodeid) - console.log(f"uploading protocol: {test_protocols['protocoluploadjson'].resolve()}") - drag_and_drop_file( - protocol_landing.get_drag_drop_file_button(), - test_protocols["protocoluploadjson"], - ) - time.sleep(3) # waiting for protocol to analyze - device_landing: DeviceLanding = DeviceLanding(driver, console, request.node.nodeid) - left_menu.navigate("devices") - # this test is against only the dev robot - robot = next(robot for robot in robots if robot.display_name == Dev.display_name) - ot_robot = OtRobot(console, robot) - console.print(f"Testing against robot {ot_robot.data.display_name}", style="white on blue") - assert ot_robot.is_alive(), "is the robot available?" - if device_landing.get_robot_banner_safe(ot_robot.data.display_name) is None: - raise AssertionError(f"Stopping the test, the robot with name {ot_robot.data.display_name} is not found.") - if device_landing.get_go_to_run_safe(ot_robot.data.display_name) is not None: - raise AssertionError(f"Stopping the test, the robot with name {ot_robot.data.display_name} has an active run.") - device_landing.click_robot_banner(ot_robot.data.display_name) - # now we are on the device detail page - # click Run a protocol button to open the slider - device_landing.click_device_detail_run_a_protocol_button() - device_landing.click_proceed_to_setup_button_device_landing_page() - # todo dynamic - time.sleep(5) - - # Verify the Setup for run page - setup_calibrate = SetupCalibration(driver, console, request.node.nodeid) - assert setup_calibrate.get_robot_calibration().text == "Robot Calibration" - setup_calibrate.click_robot_calibration() - assert setup_calibrate.get_deck_calibration().text == "Deck Calibration" - assert setup_calibrate.get_required_pipettes().text == "Required Pipettes" - assert setup_calibrate.get_calibration_ready_locator().text == "Calibration Ready" - assert setup_calibrate.get_required_tip_length_calibration().text == "Required Tip Length Calibrations" - module_setup = ModuleSetup(driver, console, request.node.nodeid) - assert module_setup.get_proceed_to_module_setup().is_displayed() - module_setup.click_proceed_to_module_setup() - assert module_setup.get_module_setup_text_locator().text == "Module Setup" - assert module_setup.get_thermocycler_module().text == "Thermocycler Module" - assert module_setup.get_magnetic_module().text == "Magnetic Module GEN1" - assert module_setup.get_temperature_module().text == "Temperature Module GEN1" - assert module_setup.get_proceed_to_labware_setup().is_displayed() - module_setup.click_proceed_to_labware_setup() - labware_setup = LabwareSetup(driver, console, request.node.nodeid) - assert labware_setup.get_labware_setup_text().is_displayed() - labware_setup.click_proceed_to_run_button() - device_landing.click_start_run_button() - assert device_landing.get_run_button().is_displayed() - assert device_landing.get_success_banner_run_page().is_displayed() - - # Uncurrent the run from the robot - assert protocol_landing.get_close_button_uncurrent_run().is_displayed() - protocol_landing.click_close_button_uncurrent_run() diff --git a/app-testing/tests/labware_landing_test.py b/app-testing/tests/labware_landing_test.py deleted file mode 100644 index 73dfd1c87ca..00000000000 --- a/app-testing/tests/labware_landing_test.py +++ /dev/null @@ -1,80 +0,0 @@ -"""Test the Labware Landing of the page.""" - -from pathlib import Path -from typing import Dict, List - -import pytest -from automation.driver.drag_drop import drag_and_drop_file -from automation.menus.left_menu import LeftMenu -from automation.pages.labware_landing import LabwareLanding -from automation.resources.robot_data import RobotDataType -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver - - -@pytest.mark.skip("Need to fix.") -def test_labware_landing( - driver: WebDriver, - console: Console, - test_labwares: Dict[str, Path], - robots: List[RobotDataType], - request: pytest.FixtureRequest, -) -> None: - """Validate some of the functionality of the labware page.""" - # Instantiate the page object for the App settings. - labware_landing: LabwareLanding = LabwareLanding(driver, console, request.node.nodeid) - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - - # Labware Landing Page - left_menu.navigate("labware") - assert labware_landing.get_labware_header().text == "Labware" - assert labware_landing.get_labware_image().is_displayed() - assert labware_landing.get_labware_name().is_displayed() - assert labware_landing.get_api_name().is_displayed() - assert labware_landing.get_import_button().is_displayed() - - assert labware_landing.get_open_labware_creator().get_attribute("href") == "https://labware.opentrons.com/create/" - - labware_landing.click_import_button() - assert labware_landing.get_import_custom_labware_definition_header().is_displayed() - assert labware_landing.get_choose_file_button().is_displayed() - console.print( - f"uploading labware: {test_labwares['validlabware'].resolve()}", - style="white on blue", - ) - drag_and_drop_file(labware_landing.get_drag_drop_file_button(), test_labwares["validlabware"]) - toast = labware_landing.get_success_toast_message() - if toast: - assert toast.is_displayed() - else: - labware_landing.base.take_screenshot("Labware upload success toast?") - raise AssertionError("No labware upload success toast.") - - # uploading an invalid labware and verifying the error toast - - labware_landing.click_import_button() - assert labware_landing.get_import_custom_labware_definition_header().is_displayed() - assert labware_landing.get_choose_file_button().is_displayed() - console.print( - f"uploading labware: {test_labwares['invalidlabware'].resolve()}", - style="white on blue", - ) - drag_and_drop_file(labware_landing.get_drag_drop_file_button(), test_labwares["invalidlabware"]) - assert labware_landing.get_error_toast_message().is_displayed() - - # uploading a duplicate labware and verifying the duplicate error toast - - labware_landing.click_import_button() - assert labware_landing.get_import_custom_labware_definition_header().is_displayed() - assert labware_landing.get_choose_file_button().is_displayed() - console.print( - f"uploading labware: {test_labwares['validlabware'].resolve()}", - style="white on blue", - ) - drag_and_drop_file(labware_landing.get_drag_drop_file_button(), test_labwares["validlabware"]) - toast = labware_landing.get_duplicate_error_toast_message() - if toast: - assert toast.is_displayed() - else: - labware_landing.base.take_screenshot("Labware upload duplicate toast?") - raise AssertionError("No labware upload duplicate toast.") diff --git a/app-testing/tests/lpc_test.py b/app-testing/tests/lpc_test.py deleted file mode 100644 index ebdce66ce0f..00000000000 --- a/app-testing/tests/lpc_test.py +++ /dev/null @@ -1,120 +0,0 @@ -"""todo these tests for refactoring""" - -# flake8: noqa -import time -from pathlib import Path -from typing import Dict, List - -import pytest -from pytest import FixtureRequest -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver - -from automation.driver.drag_drop import drag_and_drop_file -from automation.menus.left_menu import LeftMenu -from automation.pages.labware_position_check import LabwarePositionCheck -from automation.pages.labware_setup import LabwareSetup -from automation.pages.protocol_landing import ProtocolLanding -from automation.resources.ot_robot import OtRobot -from automation.resources.robot_data import Dev, RobotDataType - - -@pytest.mark.skip("Need to fix.") -def test_LPC_flow( - driver: WebDriver, - console: Console, - test_protocols: Dict[str, Path], - robots: List[RobotDataType], - request: FixtureRequest, -) -> None: - """Upload a protocol.""" - # this test is against only the dev robot - robot = next(robot for robot in robots if robot.display_name == Dev.display_name) - ot_robot = OtRobot(console, robot) - console.print(f"Testing against robot {ot_robot.data.display_name}", style="white on blue") - assert ot_robot.is_alive(), "is the robot available?" - - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - left_menu.navigate("protocols") - - # Verifying elements on the protocol page - protocol_landing: ProtocolLanding = ProtocolLanding(driver, console, request.node.nodeid) - console.print( - f"uploading protocol: {test_protocols['protocoluploadjson'].resolve()}", - style="white on blue", - ) - drag_and_drop_file( - protocol_landing.get_drag_drop_file_button(), - test_protocols["protocoluploadjson"], - ) - time.sleep(5) # todo need dynamic wait here - - protocol_landing.click_overflow_menu() - protocol_landing.click_run_on_protocol_landing() - protocol_landing.click_proceed_to_setup_on_protocol_detail() - time.sleep(8) # todo need dynamic wait here - labware_setup: LabwareSetup = LabwareSetup(driver, console, request.node.nodeid) - labware_setup.click_labware_setup_text() - - labware_position_check: LabwarePositionCheck = LabwarePositionCheck(driver, console, request.node.nodeid) - - if labware_position_check.get_ignored_stored_data(): - labware_position_check.click_ignored_stored_data() - - labware_position_check.click_labware_position_button() - assert labware_position_check.get_introScreen_labware_position_check_overview().is_displayed() - labware_position_check.click_begin_labware_position_check_button() - assert labware_position_check.get_how_to_tell_pipette_is_centered_link().is_displayed() - labware_position_check.click_how_to_tell_pipette_is_centered_link() - labware_setup.click_close_button() - labware_position_check.click_reveal_all_jog_controls() - labware_position_check.click_back_jog_button() - labware_position_check.click_down_jog_button() - labware_position_check.click_right_jog_button() - labware_position_check.click_forward_jog_button() - labware_position_check.click_confirm_position_button_pickup_tip() - labware_position_check.click_confirm_position_moveto_slot_5() - assert labware_position_check.get_how_to_tell_pipette_is_centered_link().is_displayed() - labware_position_check.click_reveal_all_jog_controls() - labware_position_check.click_back_jog_button() - labware_position_check.click_down_jog_button() - labware_position_check.click_right_jog_button() - labware_position_check.click_forward_jog_button() - labware_position_check.click_confirm_position_moveto_slot_6() - assert labware_position_check.get_how_to_tell_pipette_is_centered_link().is_displayed() - labware_position_check.click_reveal_all_jog_controls() - labware_position_check.click_back_jog_button() - labware_position_check.click_down_jog_button() - labware_position_check.click_right_jog_button() - labware_position_check.click_forward_jog_button() - labware_position_check.click_confirm_position_returntip_slot_home() - assert labware_position_check.get_labware_position_check_complete().is_displayed() - assert labware_position_check.get_deckmap_labware_check_complete().is_displayed() - assert labware_position_check.get_section_list_step0().is_displayed() - assert labware_position_check.get_section_list_step1().is_displayed() - assert labware_position_check.get_section_list_step2().is_displayed() - assert labware_position_check.get_close_and_apply_labware_offset_data_button().is_displayed() - labware_position_check.click_get_close_and_apply_labware_offset_data_button() - # assert labware_position_check.get_labware_success_toast().is_displayed() - assert labware_position_check.get_labware_display_name_slot_4().text == "Opentrons 96 Tip Rack 300 µL" - assert labware_position_check.get_labware_offsetbox_slot_4().is_displayed() - assert labware_position_check.get_labware_slot_4_offset_x_text().is_displayed() - assert labware_position_check.get_labware_slot_4_offset_x_value().text == "0.1" - assert labware_position_check.get_labware_slot_4_offset_y_text().is_displayed() - assert labware_position_check.get_labware_slot_4_offset_y_value().text == "0.0" - assert labware_position_check.get_labware_slot_4_offset_z_text().is_displayed() - assert labware_position_check.get_labware_slot_4_offset_z_value().text == "-0.1" - assert labware_position_check.get_labware_display_name_slot_5().text == "A1" - assert labware_position_check.get_labware_slot_5_offset_x_text().is_displayed() - assert labware_position_check.get_labware_slot_5_offset_x_value().text == "0.1" - assert labware_position_check.get_labware_slot_5_offset_y_text().is_displayed() - assert labware_position_check.get_labware_slot_5_offset_y_value().text == "0.0" - assert labware_position_check.get_labware_slot_5_offset_z_text().is_displayed() - assert labware_position_check.get_labware_slot_5_offset_z_value().text == "-0.1" - assert labware_position_check.get_labware_display_name_slot_2().text == "Opentrons 96 Tip Rack 10 µL" - assert labware_position_check.get_labware_slot_2_offset_x_text().is_displayed() - assert labware_position_check.get_labware_slot_2_offset_x_value().text == "0.1" - assert labware_position_check.get_labware_slot_2_offset_y_text().is_displayed() - assert labware_position_check.get_labware_slot_2_offset_y_value().text == "0.0" - assert labware_position_check.get_labware_slot_2_offset_z_text().is_displayed() - assert labware_position_check.get_labware_slot_2_offset_z_value().text == "-0.1" diff --git a/app-testing/tests/protocol_analyze_test.py b/app-testing/tests/protocol_analyze_test.py deleted file mode 100644 index cce6f0434f0..00000000000 --- a/app-testing/tests/protocol_analyze_test.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Test the Protocol Landing of the page.""" - -from typing import List, Optional - -import pytest -from automation.data.protocol import Protocol -from automation.data.protocol_registry import ProtocolRegistry -from automation.driver.drag_drop import drag_and_drop_file -from automation.menus.left_menu import LeftMenu -from automation.pages.labware_landing import LabwareLanding -from automation.pages.protocol_landing import ProtocolLanding -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver -from selenium.webdriver.remote.webelement import WebElement - - -def get_error_text(protocol_landing: ProtocolLanding, error_link: WebElement) -> str: - protocol_landing.base.click_webelement(error_link) - error_details = protocol_landing.get_popout_error().text - protocol_landing.click_popout_close() - return error_details - - -protocol_registry: ProtocolRegistry = ProtocolRegistry() -protocols_to_test: Optional[List[Protocol]] = protocol_registry.protocols_to_test - -if not protocols_to_test: - exit("No protocols to test.") - - -@pytest.mark.skip(reason="This test is deprecated in place of the test_analyses test.") -@pytest.mark.parametrize("protocol", protocols_to_test, ids=[x.short_sha for x in protocols_to_test]) -def test_analyses( - driver: WebDriver, - console: Console, - request: pytest.FixtureRequest, - protocol: Protocol, -) -> None: - """Analyze a protocol in the app and validate its details.""" - labware_landing: LabwareLanding = LabwareLanding(driver, console, request.node.nodeid) - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - protocol_landing: ProtocolLanding = ProtocolLanding(driver, console, request.node.nodeid) - - # Upload labware if any - if protocol.custom_labware: - for labware in protocol.labware_paths: - left_menu.navigate("labware") - labware_landing.click_import_button() - assert labware_landing.get_import_custom_labware_definition_header().is_displayed() - assert labware_landing.get_choose_file_button().is_displayed() - console.print( - f"uploading labware: {labware.resolve()}", - style="white on blue", - ) - drag_and_drop_file(labware_landing.get_drag_drop_file_button(), labware) - if labware_landing.get_success_toast_message(filename=labware.name) or labware_landing.get_duplicate_error_toast_message( - filename=labware.name - ): - console.print( - f"{labware.name} uploaded to app.", - style="white on blue", - ) - else: - raise AssertionError("No toast message that the labware was uploaded.") - - left_menu.base.click(left_menu.protocols) - # Clean up any protocols that did not get deleted - protocol_landing.delete_all_protocols() - console.print(f"uploading protocol: {protocol.file_path.resolve()}", style="white on blue") - drag_and_drop_file( - protocol_landing.get_drag_drop_file_button(), - protocol.file_path, - ) - - analysis_timeout: int = 61 - assert protocol_landing.wait_until_loading_data_gone( - timeout_sec=analysis_timeout - ), f"Analysis took more than {analysis_timeout} seconds." - - # look for analysis error if the protocol should have one - error_link = protocol_landing.get_error_details_safe() - if protocol.app_error: - assert error_link is not None, "No analysis error but was expecting one." - assert get_error_text(protocol_landing, error_link) == protocol.app_analysis_error - elif error_link is not None: - raise AssertionError(f"Unexpected analysis error: {get_error_text(protocol_landing, error_link)}") - - # Verifying elements on Protocol Landing Page - # todo fix next line needs to be safe and print name not found - # assert protocol_landing.get_deckMap_protocol_landing(protocol_name=protocol.protocol_name).is_displayed() - # assert protocol_landing.get_protocol_name_text_protocol_landing(protocol_name=protocol.protocol_name) == protocol.protocol_name - - # TODO validate robot - - # TODO verify modules - - # No cleanup, do at the beginning of the test. diff --git a/app-testing/tests/protocol_landing_test.py b/app-testing/tests/protocol_landing_test.py deleted file mode 100644 index 07e8801d5f7..00000000000 --- a/app-testing/tests/protocol_landing_test.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Test the Protocol Landing of the page.""" - -import time -from pathlib import Path -from typing import Dict - -import pytest -from automation.driver.drag_drop import drag_and_drop_file -from automation.menus.left_menu import LeftMenu -from automation.pages.device_landing import DeviceLanding -from automation.pages.labware_setup import LabwareSetup -from automation.pages.module_setup import ModuleSetup -from automation.pages.protocol_landing import ProtocolLanding -from automation.pages.setup_calibration import SetupCalibration -from rich.console import Console -from selenium.webdriver.chrome.webdriver import WebDriver - - -@pytest.mark.skip("Need to fix.") -def test_protocol_landing( - driver: WebDriver, - console: Console, - test_protocols: Dict[str, Path], - request: pytest.FixtureRequest, -) -> None: - """Run a protocol from the protocol page. - - Must have all calibrations done for this to run. - """ - left_menu: LeftMenu = LeftMenu(driver, console, request.node.nodeid) - left_menu.navigate("protocols") - - # Verifying elements on the protocol page - protocol_landing: ProtocolLanding = ProtocolLanding(driver, console, request.node.nodeid) - console.print( - f"uploading protocol: {test_protocols['protocoluploadjson'].resolve()}", - style="white on blue", - ) - drag_and_drop_file( - protocol_landing.get_drag_drop_file_button(), - test_protocols["protocoluploadjson"], - ) - - # Verifying elements on Protocol Landing Page - assert protocol_landing.get_import_button_protocol_landing().is_displayed() - assert protocol_landing.get_deckMap_protocol_landing(protocol_name="script_pur_sample_1").is_displayed() - assert protocol_landing.get_protocol_name_text_protocol_landing(protocol_name="script_pur_sample_1") == "script_pur_sample_1" - protocol_landing.click_overflow_menu() - assert protocol_landing.get_show_in_folder().is_displayed() - assert protocol_landing.get_run_protocol().is_displayed() - assert protocol_landing.get_delete_protocol().is_displayed() - protocol_landing.click_run_on_protocol_landing() - # todo validate overflow menu disappears - # Verify the robot slideout from protocol detail page - assert protocol_landing.get_slideout_header_on_protocol_detail().is_displayed() - # todo bug around when you click if selected it unselects - # protocol_landing.click_robot_on_protocol_detail() - protocol_landing.click_proceed_to_setup_on_protocol_detail() - time.sleep(8) - # todo need dynamic wait here - robot_calibrate = SetupCalibration(driver, console, request.node.nodeid) - assert robot_calibrate.get_robot_calibration().text == "Robot Calibration" - robot_calibrate.click_robot_calibration() - assert robot_calibrate.get_deck_calibration().text == "Deck Calibration" - assert robot_calibrate.get_required_pipettes().text == "Required Pipettes" - assert robot_calibrate.get_calibration_ready_locator().text == "Calibration Ready" - assert robot_calibrate.get_required_tip_length_calibration().text == "Required Tip Length Calibrations" - module_setup = ModuleSetup(driver, console, request.node.nodeid) - assert module_setup.get_proceed_to_module_setup().is_displayed() - module_setup.click_proceed_to_module_setup() - assert module_setup.get_module_setup_text_locator().text == "Module Setup" - assert module_setup.get_thermocycler_module().text == "Thermocycler Module GEN1" - assert module_setup.get_magnetic_module().text == "Magnetic Module GEN2" - assert module_setup.get_temperature_module().text == "Temperature Module GEN2" - assert module_setup.get_proceed_to_labware_setup().is_displayed() - module_setup.click_proceed_to_labware_setup() - labware_setup = LabwareSetup(driver, console, request.node.nodeid) - assert labware_setup.get_labware_setup_text().is_displayed() - labware_setup.click_proceed_to_run_button() - device_landing: DeviceLanding = DeviceLanding(driver, console, request.node.nodeid) - # Verify the components on run page - device_landing.click_start_run_button() - assert device_landing.get_run_button().is_displayed() - assert device_landing.get_success_banner_run_page().is_displayed() - - # Uncurrent the run from the robot - assert protocol_landing.get_close_button_uncurrent_run().is_displayed() - protocol_landing.click_close_button_uncurrent_run() diff --git a/app/src/atoms/buttons/LargeButton.stories.tsx b/app/src/atoms/buttons/LargeButton.stories.tsx index 2b52cbed5b4..9011af1ec98 100644 --- a/app/src/atoms/buttons/LargeButton.stories.tsx +++ b/app/src/atoms/buttons/LargeButton.stories.tsx @@ -1,4 +1,5 @@ -import { ICON_DATA_BY_NAME, VIEWPORT } from '@opentrons/components' +import * as React from 'react' +import { Box, COLORS, SPACING, ICON_DATA_BY_NAME } from '@opentrons/components' import { LargeButton } from './' import type { Meta, StoryObj } from '@storybook/react' @@ -15,7 +16,29 @@ const meta: Meta = { options: Object.keys(ICON_DATA_BY_NAME), }, }, - parameters: VIEWPORT.touchScreenViewport, + parameters: { + viewport: { + defaultViewport: 'Touchscreen', + }, + pseudo: { + rootSelector: '#content', + }, + }, + decorators: [ + (Story, context) => ( + + + + ), + ], } export default meta @@ -45,26 +68,13 @@ export const Alert: Story = { iconName: 'reset', }, } -export const PrimaryNoIcon: Story = { - args: { - buttonText: 'Button text', - disabled: false, - }, -} -export const PrimaryWithSubtext: Story = { - args: { - buttonText: 'Button text', - disabled: false, - subtext: 'Button subtext', - }, -} -export const OnColor: Story = { +export const AlertStroke: Story = { args: { - buttonType: 'onColor', + buttonType: 'alertStroke', buttonText: 'Button text', disabled: false, - subtext: 'Button subtext', + iconName: 'ot-alert', }, } @@ -73,6 +83,6 @@ export const AlertAlt: Story = { buttonType: 'alertAlt', buttonText: 'Button text', disabled: false, - subtext: 'Button subtext', + iconName: 'ot-check', }, } diff --git a/app/src/atoms/buttons/LargeButton.tsx b/app/src/atoms/buttons/LargeButton.tsx index 789fb849f16..7238cfdc995 100644 --- a/app/src/atoms/buttons/LargeButton.tsx +++ b/app/src/atoms/buttons/LargeButton.tsx @@ -13,21 +13,19 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { ODD_FOCUS_VISIBLE } from './constants' import type { IconName, StyleProps } from '@opentrons/components' type LargeButtonTypes = | 'primary' | 'secondary' | 'alert' - | 'onColor' + | 'alertStroke' | 'alertAlt' interface LargeButtonProps extends StyleProps { onClick: () => void buttonType?: LargeButtonTypes buttonText: React.ReactNode iconName?: IconName - subtext?: string disabled?: boolean } @@ -36,7 +34,6 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { buttonType = 'primary', buttonText, iconName, - subtext, disabled = false, ...buttonProps } = props @@ -51,8 +48,11 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { disabledColor: string iconColor: string disabledIconColor: string - border?: string - disabledBorder?: string + focusVisibleOutlineColor: string + focusVisibleBackgroundColor: string + activeIconColor?: string + isInverse?: boolean + activeColor?: string } > = { secondary: { @@ -63,6 +63,8 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { disabledBackgroundColor: COLORS.grey35, iconColor: COLORS.blue50, disabledIconColor: COLORS.grey50, + focusVisibleOutlineColor: COLORS.blue50, + focusVisibleBackgroundColor: COLORS.blue40, }, alert: { defaultColor: COLORS.red60, @@ -72,6 +74,8 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { disabledBackgroundColor: COLORS.grey35, iconColor: COLORS.red60, disabledIconColor: COLORS.grey50, + focusVisibleOutlineColor: COLORS.blue50, + focusVisibleBackgroundColor: COLORS.red40, }, primary: { defaultColor: COLORS.white, @@ -81,29 +85,49 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { disabledBackgroundColor: COLORS.grey35, iconColor: COLORS.white, disabledIconColor: COLORS.grey50, + focusVisibleOutlineColor: COLORS.blue55, + focusVisibleBackgroundColor: COLORS.blue55, }, - onColor: { + alertStroke: { defaultColor: COLORS.white, disabledColor: COLORS.grey40, + activeColor: COLORS.red60, defaultBackgroundColor: COLORS.transparent, - activeBackgroundColor: COLORS.transparent, + activeBackgroundColor: COLORS.red35, disabledBackgroundColor: COLORS.transparent, iconColor: COLORS.white, disabledIconColor: COLORS.grey40, - border: `${BORDERS.borderRadius4} solid ${COLORS.white}`, - disabledBorder: `${BORDERS.borderRadius4} solid ${COLORS.grey35}`, + isInverse: true, + activeIconColor: COLORS.red60, + focusVisibleOutlineColor: COLORS.blue50, + focusVisibleBackgroundColor: COLORS.red40, }, alertAlt: { defaultColor: COLORS.red50, disabledColor: COLORS.grey50, defaultBackgroundColor: COLORS.white, - activeBackgroundColor: COLORS.white, + activeBackgroundColor: COLORS.red35, disabledBackgroundColor: COLORS.grey35, iconColor: COLORS.red50, disabledIconColor: COLORS.grey50, + activeIconColor: COLORS.red60, + activeColor: COLORS.red60, + focusVisibleOutlineColor: COLORS.blue50, + focusVisibleBackgroundColor: COLORS.red40, }, } - + const activeColorFor = ( + style: keyof typeof LARGE_BUTTON_PROPS_BY_TYPE + ): string => + LARGE_BUTTON_PROPS_BY_TYPE[style].activeColor + ? `color: ${LARGE_BUTTON_PROPS_BY_TYPE[style].activeColor}` + : '' + const activeIconStyle = ( + style: keyof typeof LARGE_BUTTON_PROPS_BY_TYPE + ): string => + LARGE_BUTTON_PROPS_BY_TYPE[style].activeIconColor + ? `color: ${LARGE_BUTTON_PROPS_BY_TYPE[style].activeIconColor}` + : '' const LARGE_BUTTON_STYLE = css` text-align: ${TYPOGRAPHY.textAlignLeft}; color: ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; @@ -114,29 +138,41 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { box-shadow: none; padding: ${SPACING.spacing24}; line-height: ${TYPOGRAPHY.lineHeight20}; - border: ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType].border}; + gap: ${SPACING.spacing60}; + border: ${BORDERS.borderRadius4} solid + ${!!LARGE_BUTTON_PROPS_BY_TYPE[buttonType].isInverse + ? LARGE_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor + : LARGE_BUTTON_PROPS_BY_TYPE[buttonType].defaultBackgroundColor}; + ${TYPOGRAPHY.pSemiBold} - &:focus { + #btn-icon: { + color: ${disabled + ? LARGE_BUTTON_PROPS_BY_TYPE[buttonType].disabledIconColor + : LARGE_BUTTON_PROPS_BY_TYPE[buttonType].iconColor}; + } + + &:active { background-color: ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType] .activeBackgroundColor}; - box-shadow: none; + ${activeColorFor(buttonType)}; + border: ${BORDERS.borderRadius4} solid + ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType].activeBackgroundColor}; } - &:hover { - border: ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType].border}; - box-shadow: none; - background-color: ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType] - .defaultBackgroundColor}; - color: ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; + &:active #btn-icon { + ${activeIconStyle(buttonType)}; } + &:focus-visible { - box-shadow: ${ODD_FOCUS_VISIBLE}; background-color: ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType] - .defaultBackgroundColor}; - } - &:active { - background-color: ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType] - .activeBackgroundColor}; + .focusVisibleBackgroundColor}; + ${activeColorFor(buttonType)}; + padding: calc(${SPACING.spacing24} + ${SPACING.spacing2}); + border: ${SPACING.spacing2} solid ${COLORS.transparent}; + outline: 3px solid + ${LARGE_BUTTON_PROPS_BY_TYPE[buttonType].focusVisibleOutlineColor}; + background-clip: padding-box; + box-shadow: none; } &:disabled { @@ -158,22 +194,13 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { {buttonText} - {subtext ? ( - - {subtext} - - ) : null} {iconName ? ( ) : null} diff --git a/app/src/atoms/buttons/__tests__/LargeButton.test.tsx b/app/src/atoms/buttons/__tests__/LargeButton.test.tsx index 7d7c730b636..1ab29b275d5 100644 --- a/app/src/atoms/buttons/__tests__/LargeButton.test.tsx +++ b/app/src/atoms/buttons/__tests__/LargeButton.test.tsx @@ -46,10 +46,10 @@ describe('LargeButton', () => { ) }) - it('renders the onColor button', () => { + it('renders the alertStroke button', () => { props = { ...props, - buttonType: 'onColor', + buttonType: 'alertStroke', } render(props) expect(screen.getByRole('button')).toHaveStyle( diff --git a/app/src/organisms/ErrorRecoveryFlows/RunPausedSplash.tsx b/app/src/organisms/ErrorRecoveryFlows/RunPausedSplash.tsx index f3a563e2b93..c1691cd5ed4 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RunPausedSplash.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RunPausedSplash.tsx @@ -116,7 +116,7 @@ export function RunPausedSplash( buttonText={t('launch_recovery_mode')} css={SHARED_BUTTON_STYLE} iconName={'recovery'} - buttonType="onColor" + buttonType="alertStroke" /> diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/RunPausedSplash.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/RunPausedSplash.test.tsx index d8be18432d7..40a47536303 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/RunPausedSplash.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/RunPausedSplash.test.tsx @@ -4,8 +4,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { fireEvent, screen, waitFor, renderHook } from '@testing-library/react' import { createStore } from 'redux' -import { COLORS } from '@opentrons/components' - import { renderWithProviders } from '../../../__testing-utils__' import { i18n } from '../../../i18n' import { mockRecoveryContentProps } from '../__fixtures__' @@ -105,16 +103,6 @@ describe('RunPausedSplash', () => { expect(primaryBtn).toBeInTheDocument() expect(secondaryBtn).toBeInTheDocument() - expect(primaryBtn).toHaveStyle({ 'background-color': 'transparent' }) - expect(secondaryBtn).toHaveStyle({ 'background-color': COLORS.white }) - - expect(screen.getByLabelText('remove icon')).toHaveStyle({ - color: COLORS.red50, - }) - expect(screen.getByLabelText('recovery icon')).toHaveStyle({ - color: COLORS.white, - }) - fireEvent.click(secondaryBtn) await waitFor(() => { diff --git a/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx b/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx index 705dcaf01f1..711de46a9a0 100644 --- a/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx @@ -10,7 +10,7 @@ import { ALIGN_CENTER, } from '@opentrons/components' -import { LargeButton, TabbedButton } from '../../atoms/buttons' +import { RadioButton, TabbedButton } from '../../atoms/buttons' import { ChildNavigation } from '../ChildNavigation' import { getCompatibleLabwareByCategory } from './utils' @@ -114,32 +114,30 @@ export function SelectDestLabware( marginTop="175px" > {selectedCategory === 'all' && state?.source != null ? ( - { + { setSelectedLabware('source') }} - buttonText={t('source_labware_d2')} - subtext={state.source.metadata.displayName} + buttonLabel={t('source_labware_d2')} + buttonValue="source-labware-d2" + subButtonLabel={state.source.metadata.displayName} /> ) : null} {compatibleLabwareDefinitions?.map(definition => { return definition.metadata.displayName != null ? ( - { + onChange={() => { setSelectedLabware(definition) }} - buttonText={definition.metadata.displayName} + buttonValue={definition.metadata.displayName} + buttonLabel={definition.metadata.displayName} /> ) : null })} diff --git a/app/src/organisms/QuickTransferFlow/SelectPipette.tsx b/app/src/organisms/QuickTransferFlow/SelectPipette.tsx index 546000e286d..bafce8ced26 100644 --- a/app/src/organisms/QuickTransferFlow/SelectPipette.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectPipette.tsx @@ -9,7 +9,7 @@ import { } from '@opentrons/components' import { useInstrumentsQuery } from '@opentrons/react-api-client' import { getPipetteSpecsV2, RIGHT, LEFT } from '@opentrons/shared-data' -import { LargeButton } from '../../atoms/buttons' +import { RadioButton } from '../../atoms/buttons' import { ChildNavigation } from '../ChildNavigation' import type { PipetteData, Mount } from '@opentrons/api-client' @@ -89,27 +89,29 @@ export function SelectPipette(props: SelectPipetteProps): JSX.Element { {t('pipette_currently_attached')} {leftPipetteSpecs != null ? ( - { + { setSelectedPipette(LEFT) }} - buttonText={ + buttonValue={LEFT} + buttonLabel={ leftPipetteSpecs.channels === 96 ? t('both_mounts') : t('left_mount') } - subtext={leftPipetteSpecs.displayName} + subButtonLabel={leftPipetteSpecs.displayName} /> ) : null} {rightPipetteSpecs != null ? ( - { + { setSelectedPipette(RIGHT) }} - buttonText={t('right_mount')} - subtext={rightPipetteSpecs.displayName} + buttonValue={RIGHT} + buttonLabel={t('right_mount')} + subButtonLabel={rightPipetteSpecs.displayName} /> ) : null} diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx index b070ce22443..b2d44432095 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx @@ -10,7 +10,7 @@ import { ALIGN_CENTER, } from '@opentrons/components' -import { LargeButton, TabbedButton } from '../../atoms/buttons' +import { RadioButton, TabbedButton } from '../../atoms/buttons' import { ChildNavigation } from '../ChildNavigation' import { getCompatibleLabwareByCategory } from './utils' @@ -116,18 +116,17 @@ export function SelectSourceLabware( > {compatibleLabwareDefinitions?.map(definition => { return definition.metadata.displayName != null ? ( - { + onChange={() => { setSelectedLabware(definition) }} - buttonText={definition.metadata.displayName} + buttonValue={definition.metadata.displayName} + buttonLabel={definition.metadata.displayName} /> ) : null })} diff --git a/app/src/organisms/QuickTransferFlow/SelectTipRack.tsx b/app/src/organisms/QuickTransferFlow/SelectTipRack.tsx index acd5e90b054..fa664eafcc4 100644 --- a/app/src/organisms/QuickTransferFlow/SelectTipRack.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectTipRack.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import { Flex, SPACING, DIRECTION_COLUMN } from '@opentrons/components' import { getAllDefinitions } from '@opentrons/shared-data' -import { LargeButton } from '../../atoms/buttons' +import { RadioButton } from '../../atoms/buttons' import { ChildNavigation } from '../ChildNavigation' import type { LabwareDefinition2 } from '@opentrons/shared-data' @@ -64,15 +64,14 @@ export function SelectTipRack(props: SelectTipRackProps): JSX.Element { const tipRackDef = allLabwareDefinitionsByUri[tipRack] return tipRackDef != null ? ( - { + isSelected={selectedTipRack === tipRackDef} + buttonValue={tipRack} + buttonLabel={tipRackDef.metadata.displayName} + onChange={() => { setSelectedTipRack(tipRackDef) }} - buttonText={tipRackDef.metadata.displayName} /> ) : null })} diff --git a/app/src/organisms/QuickTransferFlow/TipManagement/ChangeTip.tsx b/app/src/organisms/QuickTransferFlow/TipManagement/ChangeTip.tsx index b0204dbf94e..32a2ea9111c 100644 --- a/app/src/organisms/QuickTransferFlow/TipManagement/ChangeTip.tsx +++ b/app/src/organisms/QuickTransferFlow/TipManagement/ChangeTip.tsx @@ -9,7 +9,7 @@ import { COLORS, } from '@opentrons/components' import { getTopPortalEl } from '../../../App/portal' -import { LargeButton } from '../../../atoms/buttons' +import { RadioButton } from '../../../atoms/buttons' import { ChildNavigation } from '../../ChildNavigation' import type { @@ -72,15 +72,14 @@ export function ChangeTip(props: ChangeTipProps): JSX.Element { width="100%" > {allowedChangeTipOptions.map(option => ( - { + isSelected={selectedChangeTipOption === option} + onChange={() => { setSelectedChangeTipOption(option) }} - buttonText={t(`${option}`)} + buttonValue={option} + buttonLabel={t(`${option}`)} /> ))} diff --git a/app/src/organisms/QuickTransferFlow/TipManagement/TipDropLocation.tsx b/app/src/organisms/QuickTransferFlow/TipManagement/TipDropLocation.tsx index 16b02159171..e6441c37f01 100644 --- a/app/src/organisms/QuickTransferFlow/TipManagement/TipDropLocation.tsx +++ b/app/src/organisms/QuickTransferFlow/TipManagement/TipDropLocation.tsx @@ -14,7 +14,7 @@ import { TRASH_BIN_ADAPTER_FIXTURE, } from '@opentrons/shared-data' import { getTopPortalEl } from '../../../App/portal' -import { LargeButton } from '../../../atoms/buttons' +import { RadioButton } from '../../../atoms/buttons' import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' import { ChildNavigation } from '../../ChildNavigation' @@ -80,17 +80,14 @@ export function TipDropLocation(props: TipDropLocationProps): JSX.Element { width="100%" > {tipDropLocationOptions.map(option => ( - { + isSelected={selectedTipDropLocation.cutoutId === option.cutoutId} + onChange={() => { setSelectedTipDropLocation(option) }} - buttonText={t( + buttonValue={option.cutoutId} + buttonLabel={t( `${ option.cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE ? 'trashBin' diff --git a/hardware-testing/hardware_testing/gravimetric/config.py b/hardware-testing/hardware_testing/gravimetric/config.py index 909739c7b31..3e5ebcca3bb 100644 --- a/hardware-testing/hardware_testing/gravimetric/config.py +++ b/hardware-testing/hardware_testing/gravimetric/config.py @@ -92,7 +92,7 @@ class PhotometricConfig(VolumetricConfig): "max_z_distance": 20, "mount_speed": 11, "plunger_speed": 21, - "sensor_threshold_pascals": 150, + "sensor_threshold_pascals": 15, }, }, 8: { @@ -100,7 +100,7 @@ class PhotometricConfig(VolumetricConfig): "max_z_distance": 20, "mount_speed": 11, "plunger_speed": 21, - "sensor_threshold_pascals": 150, + "sensor_threshold_pascals": 15, }, }, }, @@ -110,19 +110,19 @@ class PhotometricConfig(VolumetricConfig): "max_z_distance": 20, "mount_speed": 5, "plunger_speed": 10, - "sensor_threshold_pascals": 200, + "sensor_threshold_pascals": 15, }, 200: { "max_z_distance": 20, "mount_speed": 5, "plunger_speed": 10, - "sensor_threshold_pascals": 200, + "sensor_threshold_pascals": 15, }, 1000: { "max_z_distance": 20, "mount_speed": 5, "plunger_speed": 11, - "sensor_threshold_pascals": 150, + "sensor_threshold_pascals": 15, }, }, 8: { @@ -130,19 +130,19 @@ class PhotometricConfig(VolumetricConfig): "max_z_distance": 20, "mount_speed": 5, "plunger_speed": 10, - "sensor_threshold_pascals": 200, + "sensor_threshold_pascals": 15, }, 200: { "max_z_distance": 20, "mount_speed": 5, "plunger_speed": 10, - "sensor_threshold_pascals": 200, + "sensor_threshold_pascals": 15, }, 1000: { "max_z_distance": 20, "mount_speed": 5, "plunger_speed": 11, - "sensor_threshold_pascals": 150, + "sensor_threshold_pascals": 15, }, }, 96: { @@ -150,19 +150,19 @@ class PhotometricConfig(VolumetricConfig): "max_z_distance": 20, "mount_speed": 5, "plunger_speed": 10, - "sensor_threshold_pascals": 200, + "sensor_threshold_pascals": 15, }, 200: { "max_z_distance": 20, "mount_speed": 5, "plunger_speed": 10, - "sensor_threshold_pascals": 200, + "sensor_threshold_pascals": 15, }, 1000: { "max_z_distance": 20, "mount_speed": 5, "plunger_speed": 11, - "sensor_threshold_pascals": 150, + "sensor_threshold_pascals": 15, }, }, }, diff --git a/hardware-testing/hardware_testing/liquid_sense/__main__.py b/hardware-testing/hardware_testing/liquid_sense/__main__.py index b1fc67a2d2e..afd23ee9721 100644 --- a/hardware-testing/hardware_testing/liquid_sense/__main__.py +++ b/hardware-testing/hardware_testing/liquid_sense/__main__.py @@ -40,13 +40,11 @@ ) try: - from abr_testing.automation import google_sheets_tool + from abr_testing.automation import google_sheets_tool, google_drive_tool except ImportError: ui.print_error( "Unable to import abr repo if this isn't a simulation push the abr_testing package" ) - from . import google_sheets_tool # type: ignore[no-redef] - pass CREDENTIALS_PATH = "/var/lib/jupyter/notebooks/abr.json" @@ -119,6 +117,7 @@ class RunArgs: plunger_speed: float trials_before_jog: int multi_passes: int + test_well: str @classmethod def _get_protocol_context(cls, args: argparse.Namespace) -> ProtocolContext: @@ -252,6 +251,7 @@ def build_run_args(cls, args: argparse.Namespace) -> "RunArgs": plunger_speed=args.plunger_speed, trials_before_jog=args.trials_before_jog, multi_passes=args.multi_passes, + test_well=args.test_well, ) @@ -271,6 +271,7 @@ def build_run_args(cls, args: argparse.Namespace) -> "RunArgs": parser.add_argument("--plunger-speed", type=float, default=-1.0) parser.add_argument("--multi-passes", type=int, default=1) parser.add_argument("--starting-tip", type=str, default="A1") + parser.add_argument("--test-well", type=str, default="A1") parser.add_argument("--google-sheet-name", type=str, default="LLD-Shared-Data") parser.add_argument( "--gd-parent-folder", type=str, default="1b2V85fDPA0tNqjEhyHOGCWRZYgn8KsGf" @@ -306,7 +307,6 @@ def build_run_args(cls, args: argparse.Namespace) -> "RunArgs": sheet_id = google_sheet.create_worksheet(run_args.run_id) # type: ignore[union-attr] try: sys.path.insert(0, "/var/lib/jupyter/notebooks/") - import google_drive_tool # type: ignore[import] google_drive: Optional[ google_drive_tool.google_drive @@ -347,33 +347,33 @@ def build_run_args(cls, args: argparse.Namespace) -> "RunArgs": new_folder_name = ( f"MS{args.z_speed}_PS{args.plunger_speed}_{run_args.run_id}" ) - process_csv_directory( - f"{data_dir}/{run_args.name}/{run_args.run_id}", - run_args.tip_volumes, - run_args.trials, - google_sheet, - google_drive, - run_args.run_id, - sheet_id, - new_folder_name, - make_graph=True, - ) - # Log to Google Sheet - if args.aspirate is False: - plunger_direction = "dispense" - else: - plunger_direction = "aspirate" - test_info = [ - run_args.run_id, - run_args.pipette_tag, - args.pipette, - args.tip, - args.z_speed, - args.plunger_speed, - "threshold", - plunger_direction, - ] try: + process_csv_directory( + f"{data_dir}/{run_args.name}/{run_args.run_id}", + run_args.tip_volumes, + run_args.trials, + google_sheet, + google_drive, + run_args.run_id, + sheet_id, + new_folder_name, + make_graph=True, + ) + # Log to Google Sheet + if args.aspirate is False: + plunger_direction = "dispense" + else: + plunger_direction = "aspirate" + test_info = [ + run_args.run_id, + run_args.pipette_tag, + args.pipette, + args.tip, + args.z_speed, + args.plunger_speed, + "threshold", + plunger_direction, + ] process_google_sheet(google_sheet, run_args, test_info, sheet_id) except Exception as e: ui.print_error("error making graphs or logging data on google sheet") diff --git a/hardware-testing/hardware_testing/liquid_sense/execute.py b/hardware-testing/hardware_testing/liquid_sense/execute.py index 027d25bc633..f4934ab021e 100644 --- a/hardware-testing/hardware_testing/liquid_sense/execute.py +++ b/hardware-testing/hardware_testing/liquid_sense/execute.py @@ -189,7 +189,7 @@ def run( liquid_height: float = 0.0 liquid_height_from_deck: float = 0.0 hw_api = get_sync_hw_api(run_args.ctx) - test_well: Well = test_labware["A1"] + test_well: Well = test_labware[run_args.test_well] _load_tipracks(run_args.ctx, run_args.pipette_channels, run_args.protocol_cfg, tip) tips: List[Well] = get_unused_tips( ctx=run_args.ctx, tip_volume=tip, pipette_mount="" diff --git a/hardware/opentrons_hardware/firmware_bindings/constants.py b/hardware/opentrons_hardware/firmware_bindings/constants.py index 848107cdc8b..90898877463 100644 --- a/hardware/opentrons_hardware/firmware_bindings/constants.py +++ b/hardware/opentrons_hardware/firmware_bindings/constants.py @@ -374,6 +374,7 @@ class SensorOutputBinding(int, Enum): sync = 0x01 report = 0x02 max_threshold_sync = 0x04 + auto_baseline_report = 0x08 @unique diff --git a/hardware/opentrons_hardware/hardware_control/tool_sensors.py b/hardware/opentrons_hardware/hardware_control/tool_sensors.py index ca059512cac..49422cf03ae 100644 --- a/hardware/opentrons_hardware/hardware_control/tool_sensors.py +++ b/hardware/opentrons_hardware/hardware_control/tool_sensors.py @@ -173,6 +173,20 @@ async def run_sync_buffer_to_csv( """Runs the sensor pass move group and creates a csv file with the results.""" sensor_metadata = [0, 0, mount_speed, plunger_speed, threshold] positions = await move_group.run(can_messenger=messenger) + # wait a little to see the dropoff curve + await asyncio.sleep(0.15) + for sensor_id in log_files.keys(): + await messenger.ensure_send( + node_id=tool, + message=BindSensorOutputRequest( + payload=BindSensorOutputRequestPayload( + sensor=SensorTypeField(sensor_type), + sensor_id=SensorIdField(sensor_id), + binding=SensorOutputBindingField(SensorOutputBinding.none), + ) + ), + expected_nodes=[tool], + ) for sensor_id in log_files.keys(): sensor_capturer = LogListener( mount=head_node, @@ -182,27 +196,20 @@ async def run_sync_buffer_to_csv( ) async with sensor_capturer: messenger.add_listener(sensor_capturer, None) + request = SendAccumulatedSensorDataRequest( + payload=SendAccumulatedSensorDataPayload( + sensor_id=SensorIdField(sensor_id), + sensor_type=SensorTypeField(sensor_type), + ) + ) await messenger.send( node_id=tool, - message=SendAccumulatedSensorDataRequest( - payload=SendAccumulatedSensorDataPayload( - sensor_id=SensorIdField(sensor_id), - sensor_type=SensorTypeField(sensor_type), - ) - ), + message=request, + ) + await sensor_capturer.wait_for_complete( + message_index=request.payload.message_index.value ) - await sensor_capturer.wait_for_complete() messenger.remove_listener(sensor_capturer) - await messenger.send( - node_id=tool, - message=BindSensorOutputRequest( - payload=BindSensorOutputRequestPayload( - sensor=SensorTypeField(sensor_type), - sensor_id=SensorIdField(sensor_id), - binding=SensorOutputBindingField(SensorOutputBinding.none), - ) - ), - ) return positions @@ -231,7 +238,7 @@ async def run_stream_output_to_csv( binding_field = SensorOutputBindingField.from_flags(binding) for sensor_id in sensors.keys(): sensor_info = sensors[sensor_id].sensor - await messenger.send( + await messenger.ensure_send( node_id=sensor_info.node_id, message=BindSensorOutputRequest( payload=BindSensorOutputRequestPayload( @@ -240,6 +247,7 @@ async def run_stream_output_to_csv( binding=binding_field, ) ), + expected_nodes=[sensor_info.node_id], ) messenger.add_listener(sensor_capturer, None) @@ -249,7 +257,7 @@ async def run_stream_output_to_csv( for sensor_id in sensors.keys(): sensor_info = sensors[sensor_id].sensor - await messenger.send( + await messenger.ensure_send( node_id=sensor_info.node_id, message=BindSensorOutputRequest( payload=BindSensorOutputRequestPayload( @@ -258,6 +266,7 @@ async def run_stream_output_to_csv( binding=SensorOutputBindingField(SensorOutputBinding.none), ) ), + expected_nodes=[sensor_info.node_id], ) return positions @@ -337,7 +346,7 @@ async def _run_with_binding( binding_field = SensorOutputBindingField.from_flags(binding) for sensor_id in sensors.keys(): sensor_info = sensors[sensor_id].sensor - await messenger.send( + await messenger.ensure_send( node_id=sensor_info.node_id, message=BindSensorOutputRequest( payload=BindSensorOutputRequestPayload( @@ -346,12 +355,13 @@ async def _run_with_binding( binding=binding_field, ) ), + expected_nodes=[sensor_info.node_id], ) result = await sensor_runner.run(can_messenger=messenger) for sensor_id in sensors.keys(): sensor_info = sensors[sensor_id].sensor - await messenger.send( + await messenger.ensure_send( node_id=sensor_info.node_id, message=BindSensorOutputRequest( payload=BindSensorOutputRequestPayload( @@ -360,6 +370,7 @@ async def _run_with_binding( binding=SensorOutputBindingField(SensorOutputBinding.none), ) ), + expected_nodes=[sensor_info.node_id], ) return result diff --git a/hardware/opentrons_hardware/sensors/sensor_driver.py b/hardware/opentrons_hardware/sensors/sensor_driver.py index ac34dde2afe..ac40ddf0c11 100644 --- a/hardware/opentrons_hardware/sensors/sensor_driver.py +++ b/hardware/opentrons_hardware/sensors/sensor_driver.py @@ -4,7 +4,8 @@ import csv from typing import Optional, AsyncIterator, Any, Sequence -from contextlib import asynccontextmanager +from contextlib import asynccontextmanager, suppress +from logging import getLogger from opentrons_hardware.drivers.can_bus.can_messenger import ( CanMessenger, @@ -46,6 +47,8 @@ from .sensor_abc import AbstractSensorDriver from .scheduler import SensorScheduler +LOG = getLogger(__name__) + class SensorDriver(AbstractSensorDriver): """Generic Sensor Driver.""" @@ -250,14 +253,16 @@ async def __aexit__(self, *args: Any) -> None: """Close csv file.""" self.data_file.close() - async def wait_for_complete(self, wait_time: float = 2.0) -> None: + async def wait_for_complete( + self, wait_time: float = 10, message_index: int = 0 + ) -> None: """Wait for the data to stop, only use this with a send_accumulated_data_request.""" self.event = asyncio.Event() - recieving = True - while recieving: - await asyncio.sleep(wait_time) - recieving = self.event.is_set() - self.event.clear() + self.expected_ack = message_index + with suppress(asyncio.TimeoutError): + await asyncio.wait_for(self.event.wait(), wait_time) + if not self.event.is_set(): + LOG.error("Did not receive the full data set from the sensor") self.event = None def __call__( @@ -273,5 +278,9 @@ def __call__( self.response_queue.put_nowait(data) current_time = round((time.time() - self.start_time), 3) self.csv_writer.writerow([current_time, data]) # type: ignore - if self.event is not None: + if isinstance(message, message_definitions.Acknowledgement): + if ( + self.event is not None + and message.payload.message_index.value == self.expected_ack + ): self.event.set() diff --git a/robot-server/robot_server/data_files/__init__.py b/robot-server/robot_server/data_files/__init__.py new file mode 100644 index 00000000000..b4181b3eee5 --- /dev/null +++ b/robot-server/robot_server/data_files/__init__.py @@ -0,0 +1 @@ +"""Upload and management of data files.""" diff --git a/robot-server/robot_server/data_files/data_files_store.py b/robot-server/robot_server/data_files/data_files_store.py new file mode 100644 index 00000000000..4d247e78ddf --- /dev/null +++ b/robot-server/robot_server/data_files/data_files_store.py @@ -0,0 +1,65 @@ +"""Store and retrieve information about uploaded data files from the database.""" +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from typing import Optional, List + +import sqlalchemy.engine + +from robot_server.persistence.database import sqlite_rowid +from robot_server.persistence.tables import data_files_table + + +@dataclass(frozen=True) +class DataFileInfo: + """Metadata info of a saved data file.""" + + id: str + name: str + file_hash: str + created_at: datetime + + +class DataFilesStore: + """Store and retrieve info about uploaded data files.""" + + def __init__( + self, + sql_engine: sqlalchemy.engine.Engine, + ) -> None: + """Create a new DataFilesStore.""" + self._sql_engine = sql_engine + + def get_file_info_by_hash(self, file_hash: str) -> Optional[DataFileInfo]: + """Get the ID of data file having the provided hash.""" + for file in self._sql_get_all_from_engine(): + if file.file_hash == file_hash: + return file + return None + + async def insert(self, file_info: DataFileInfo) -> None: + """Insert data file info in the database.""" + file_info_dict = { + "id": file_info.id, + "name": file_info.name, + "created_at": file_info.created_at, + "file_hash": file_info.file_hash, + } + statement = sqlalchemy.insert(data_files_table).values(file_info_dict) + with self._sql_engine.begin() as transaction: + transaction.execute(statement) + + def _sql_get_all_from_engine(self) -> List[DataFileInfo]: + statement = sqlalchemy.select(data_files_table).order_by(sqlite_rowid) + with self._sql_engine.begin() as transaction: + all_rows = transaction.execute(statement).all() + return [ + DataFileInfo( + id=sql_row.id, + name=sql_row.name, + created_at=sql_row.created_at, + file_hash=sql_row.file_hash, + ) + for sql_row in all_rows + ] diff --git a/robot-server/robot_server/data_files/dependencies.py b/robot-server/robot_server/data_files/dependencies.py new file mode 100644 index 00000000000..7d5b459de2b --- /dev/null +++ b/robot-server/robot_server/data_files/dependencies.py @@ -0,0 +1,56 @@ +"""FastAPI dependencies for data files endpoints.""" +from pathlib import Path +from asyncio import Lock as AsyncLock +from typing import Final +from anyio import Path as AsyncPath + +from fastapi import Depends +from sqlalchemy.engine import Engine as SQLEngine + +from server_utils.fastapi_utils.app_state import ( + AppState, + get_app_state, + AppStateAccessor, +) +from robot_server.persistence.fastapi_dependencies import ( + get_active_persistence_directory, + get_sql_engine, +) + +from .data_files_store import DataFilesStore + +_DATA_FILES_SUBDIRECTORY: Final = "data_files" + +_data_files_directory_init_lock = AsyncLock() +_data_files_directory_accessor = AppStateAccessor[Path]("data_files_directory") + +_data_files_store_init_lock = AsyncLock() +_data_files_store_accessor = AppStateAccessor[DataFilesStore]("data_files_store") + + +async def get_data_files_directory( + app_state: AppState = Depends(get_app_state), + persistent_directory: Path = Depends(get_active_persistence_directory), +) -> Path: + """Get the directory to save the protocol files, creating it if needed.""" + async with _data_files_directory_init_lock: + data_files_dir = _data_files_directory_accessor.get_from(app_state) + if data_files_dir is None: + data_files_dir = persistent_directory / _DATA_FILES_SUBDIRECTORY + await AsyncPath(data_files_dir).mkdir(exist_ok=True) + _data_files_directory_accessor.set_on(app_state, data_files_dir) + + return data_files_dir + + +async def get_data_files_store( + app_state: AppState = Depends(get_app_state), + sql_engine: SQLEngine = Depends(get_sql_engine), +) -> DataFilesStore: + """Get a singleton DataFilesStore to keep track of uploaded data files.""" + async with _data_files_store_init_lock: + data_files_store = _data_files_store_accessor.get_from(app_state) + if data_files_store is None: + data_files_store = DataFilesStore(sql_engine) + _data_files_store_accessor.set_on(app_state, data_files_store) + return data_files_store diff --git a/robot-server/robot_server/data_files/models.py b/robot-server/robot_server/data_files/models.py new file mode 100644 index 00000000000..f5a83d42acf --- /dev/null +++ b/robot-server/robot_server/data_files/models.py @@ -0,0 +1,14 @@ +"""Data files models.""" +from datetime import datetime + +from pydantic import Field + +from robot_server.service.json_api import ResourceModel + + +class DataFile(ResourceModel): + """A model representing an uploaded data file.""" + + id: str = Field(..., description="A unique identifier for this file.") + name: str = Field(..., description="Name of the uploaded file.") + createdAt: datetime = Field(..., description="When this data file was *uploaded*.") diff --git a/robot-server/robot_server/data_files/router.py b/robot-server/robot_server/data_files/router.py new file mode 100644 index 00000000000..56ef5a80ba1 --- /dev/null +++ b/robot-server/robot_server/data_files/router.py @@ -0,0 +1,143 @@ +"""Router for /dataFiles endpoints.""" +from datetime import datetime +from pathlib import Path +from textwrap import dedent +from typing import Optional, Literal, Union + +from fastapi import APIRouter, UploadFile, File, Form, Depends, status +from opentrons.protocol_reader import FileHasher, FileReaderWriter + +from robot_server.service.json_api import ( + SimpleBody, + PydanticResponse, +) +from robot_server.errors.error_responses import ErrorDetails, ErrorBody +from .dependencies import get_data_files_directory, get_data_files_store +from .data_files_store import DataFilesStore, DataFileInfo +from .models import DataFile +from ..protocols.dependencies import get_file_hasher, get_file_reader_writer +from ..service.dependencies import get_current_time, get_unique_id + +datafiles_router = APIRouter() + + +class MultipleDataFileSources(ErrorDetails): + """An error returned when multiple data file sources are specified in one request.""" + + id: Literal["MultipleDataFileSources"] = "MultipleDataFileSources" + title: str = "Multiple sources found for data files" + + +class NoDataFileSourceProvided(ErrorDetails): + """An error returned when no data file sources are specified in the request.""" + + id: Literal["NoDataFileSourceProvided"] = "NoDataFileSourceProvided" + title: str = "No data file source provided" + + +class FileNotFound(ErrorDetails): + """An error returned when specified file path was not found on the robot.""" + + id: Literal["FileNotFound"] = "FileNotFound" + title: str = "Specified file path not found on the robot" + + +class UnexpectedFileFormat(ErrorDetails): + """An error returned when specified file is not in expected format.""" + + id: Literal["UnexpectedFileFormat"] = "UnexpectedFileFormat" + title: str = "Unexpected file format" + + +@PydanticResponse.wrap_route( + datafiles_router.post, + path="/dataFiles", + summary="Upload a data file", + description=dedent( + """ + Upload data file(s) to your device. + """ + ), + status_code=status.HTTP_201_CREATED, + responses={ + status.HTTP_200_OK: {"model": SimpleBody[DataFile]}, + status.HTTP_201_CREATED: {"model": SimpleBody[DataFile]}, + status.HTTP_422_UNPROCESSABLE_ENTITY: { + "model": ErrorBody[ + Union[ + MultipleDataFileSources, + NoDataFileSourceProvided, + UnexpectedFileFormat, + ] + ] + }, + status.HTTP_404_NOT_FOUND: {"model": ErrorBody[FileNotFound]}, + }, +) +async def upload_data_file( + file: Optional[UploadFile] = File(default=None, description="Data file to upload"), + file_path: Optional[str] = Form( + default=None, + description="Absolute path to a file on the robot.", + alias="filePath", + ), + data_files_directory: Path = Depends(get_data_files_directory), + data_files_store: DataFilesStore = Depends(get_data_files_store), + file_reader_writer: FileReaderWriter = Depends(get_file_reader_writer), + file_hasher: FileHasher = Depends(get_file_hasher), + file_id: str = Depends(get_unique_id, use_cache=False), + created_at: datetime = Depends(get_current_time), +) -> PydanticResponse[SimpleBody[DataFile]]: + """Save the uploaded data file to persistent storage and update database.""" + if all([file, file_path]): + raise MultipleDataFileSources( + detail="Can accept either a file or a file path, not both." + ).as_error(status.HTTP_422_UNPROCESSABLE_ENTITY) + if file is None and file_path is None: + raise NoDataFileSourceProvided( + detail="You must provide either a file or a file_path in the request." + ).as_error(status.HTTP_422_UNPROCESSABLE_ENTITY) + try: + [buffered_file] = await file_reader_writer.read(files=[file or Path(file_path)]) # type: ignore[arg-type, list-item] + except FileNotFoundError as e: + raise FileNotFound(detail=str(e)).as_error(status.HTTP_404_NOT_FOUND) from e + # TODO (spp, 2024-06-18): probably also validate CSV file *contents* + if not buffered_file.name.endswith(".csv"): + raise UnexpectedFileFormat(detail="Only CSV file format is accepted.").as_error( + status.HTTP_422_UNPROCESSABLE_ENTITY + ) + file_hash = await file_hasher.hash([buffered_file]) + existing_file_info = data_files_store.get_file_info_by_hash(file_hash) + if existing_file_info: + return await PydanticResponse.create( + content=SimpleBody.construct( + data=DataFile.construct( + id=existing_file_info.id, + name=existing_file_info.name, + createdAt=existing_file_info.created_at, + ) + ), + status_code=status.HTTP_200_OK, + ) + + # TODO (spp, 2024-06-18): auto delete data files if max exceeded + await file_reader_writer.write( + directory=data_files_directory / file_id, files=[buffered_file] + ) + file_info = DataFileInfo( + id=file_id, + name=buffered_file.name, + file_hash=file_hash, + created_at=created_at, + ) + await data_files_store.insert(file_info) + return await PydanticResponse.create( + content=SimpleBody.construct( + data=DataFile.construct( + id=file_info.id, + name=file_info.name, + createdAt=created_at, + ) + ), + status_code=status.HTTP_201_CREATED, + ) diff --git a/robot-server/robot_server/persistence/tables/__init__.py b/robot-server/robot_server/persistence/tables/__init__.py index 59d06704ddb..f43c261cec5 100644 --- a/robot-server/robot_server/persistence/tables/__init__.py +++ b/robot-server/robot_server/persistence/tables/__init__.py @@ -8,6 +8,7 @@ run_table, run_command_table, action_table, + data_files_table, ) @@ -18,4 +19,5 @@ "run_table", "run_command_table", "action_table", + "data_files_table", ] diff --git a/robot-server/robot_server/protocols/protocol_store.py b/robot-server/robot_server/protocols/protocol_store.py index 15f0530e76d..1a8b207f950 100644 --- a/robot-server/robot_server/protocols/protocol_store.py +++ b/robot-server/robot_server/protocols/protocol_store.py @@ -217,7 +217,7 @@ def get_all_ids(self) -> List[str]: return protocol_ids def get_id_by_hash(self, hash: str) -> Optional[str]: - """Get all protocol hashes keyed by protocol id.""" + """Get ID of protocol corresponding to the provided hash.""" for p in self.get_all(): if p.source.content_hash == hash: return p.protocol_id diff --git a/robot-server/robot_server/router.py b/robot-server/robot_server/router.py index eec875df14f..c5c80ae777e 100644 --- a/robot-server/robot_server/router.py +++ b/robot-server/robot_server/router.py @@ -12,6 +12,7 @@ from .maintenance_runs.router import maintenance_runs_router from .modules.router import modules_router from .protocols.router import protocols_router +from .data_files.router import datafiles_router from .robot.router import robot_router from .runs.router import runs_router from .service.labware.router import router as labware_router @@ -64,6 +65,11 @@ dependencies=[Depends(check_version_header)], ) +router.include_router( + router=datafiles_router, + tags=["Data files Management"], + dependencies=[Depends(check_version_header)], +) router.include_router( router=commands_router, tags=["Simple Commands"], diff --git a/robot-server/tests/data_files/__init__.py b/robot-server/tests/data_files/__init__.py new file mode 100644 index 00000000000..77dedb4406d --- /dev/null +++ b/robot-server/tests/data_files/__init__.py @@ -0,0 +1 @@ +"""Tests for the robot_server.data_files module.""" diff --git a/robot-server/tests/data_files/test_data_files_store.py b/robot-server/tests/data_files/test_data_files_store.py new file mode 100644 index 00000000000..8533f5997ff --- /dev/null +++ b/robot-server/tests/data_files/test_data_files_store.py @@ -0,0 +1,48 @@ +"""Tests for the DataFilesStore interface.""" +import pytest +from datetime import datetime, timezone +from sqlalchemy.engine import Engine as SQLEngine + +from robot_server.data_files.data_files_store import DataFilesStore, DataFileInfo + + +@pytest.fixture +def subject(sql_engine: SQLEngine) -> DataFilesStore: + """Get a DataFilesStore test subject.""" + return DataFilesStore(sql_engine=sql_engine) + + +async def test_insert_data_file_info_and_fetch_by_hash( + subject: DataFilesStore, +) -> None: + """It should add the data file info to database.""" + data_file_info = DataFileInfo( + id="file-id", + name="file-name", + file_hash="abc123", + created_at=datetime(year=2024, month=6, day=20, tzinfo=timezone.utc), + ) + assert subject.get_file_info_by_hash("abc123") is None + await subject.insert(data_file_info) + assert subject.get_file_info_by_hash("abc123") == data_file_info + + +async def test_insert_file_info_with_existing_id( + subject: DataFilesStore, +) -> None: + """It should raise an error when trying to add the same file ID to database.""" + data_file_info1 = DataFileInfo( + id="file-id", + name="file-name", + file_hash="abc123", + created_at=datetime(year=2024, month=6, day=20, tzinfo=timezone.utc), + ) + data_file_info2 = DataFileInfo( + id="file-id", + name="file-name2", + file_hash="abc1234", + created_at=datetime(year=2024, month=6, day=20, tzinfo=timezone.utc), + ) + await subject.insert(data_file_info1) + with pytest.raises(Exception): + await subject.insert(data_file_info2) diff --git a/robot-server/tests/data_files/test_router.py b/robot-server/tests/data_files/test_router.py new file mode 100644 index 00000000000..dd0f7b3112e --- /dev/null +++ b/robot-server/tests/data_files/test_router.py @@ -0,0 +1,233 @@ +"""Tests for data_files router.""" +import io +import pytest +from datetime import datetime +from pathlib import Path + +from decoy import Decoy +from fastapi import UploadFile +from opentrons.protocol_reader import FileHasher, FileReaderWriter, BufferedFile + +from robot_server.data_files.data_files_store import DataFilesStore, DataFileInfo +from robot_server.data_files.models import DataFile +from robot_server.data_files.router import upload_data_file +from robot_server.errors.error_responses import ApiError + + +@pytest.fixture +def data_files_store(decoy: Decoy) -> DataFilesStore: + """Get a mocked out DataFilesStore interface.""" + return decoy.mock(cls=DataFilesStore) + + +@pytest.fixture +def file_hasher(decoy: Decoy) -> FileHasher: + """Get a mocked out FileHasher.""" + return decoy.mock(cls=FileHasher) + + +@pytest.fixture +def file_reader_writer(decoy: Decoy) -> FileReaderWriter: + """Get a mocked out FileReaderWriter.""" + return decoy.mock(cls=FileReaderWriter) + + +async def test_upload_new_data_file( + decoy: Decoy, + data_files_store: DataFilesStore, + file_reader_writer: FileReaderWriter, + file_hasher: FileHasher, +) -> None: + """It should store an uploaded data file to persistent storage & update the database.""" + data_files_directory = Path("/dev/null") + content = bytes("some_content", encoding="utf-8") + uploaded_file = io.BytesIO(content) + + data_file = UploadFile(filename="abc.csv", file=uploaded_file) + buffered_file = BufferedFile(name="abc.csv", contents=content, path=None) + + decoy.when( + await file_reader_writer.read(files=[data_file]) # type: ignore[list-item] + ).then_return([buffered_file]) + decoy.when(await file_hasher.hash(files=[buffered_file])).then_return("abc123") + decoy.when(data_files_store.get_file_info_by_hash("abc123")).then_return(None) + + result = await upload_data_file( + file=data_file, + file_path=None, + data_files_directory=data_files_directory, + data_files_store=data_files_store, + file_reader_writer=file_reader_writer, + file_hasher=file_hasher, + file_id="data-file-id", + created_at=datetime(year=2024, month=6, day=18), + ) + + assert result.content.data == DataFile( + id="data-file-id", + name="abc.csv", + createdAt=datetime(year=2024, month=6, day=18), + ) + assert result.status_code == 201 + decoy.verify( + await file_reader_writer.write( + directory=data_files_directory / "data-file-id", files=[buffered_file] + ), + await data_files_store.insert( + DataFileInfo( + id="data-file-id", + name="abc.csv", + file_hash="abc123", + created_at=datetime(year=2024, month=6, day=18), + ) + ), + ) + + +async def test_upload_existing_data_file( + decoy: Decoy, + data_files_store: DataFilesStore, + file_reader_writer: FileReaderWriter, + file_hasher: FileHasher, +) -> None: + """It should return the existing file info.""" + data_files_directory = Path("/dev/null") + content = bytes("some_content", encoding="utf-8") + uploaded_file = io.BytesIO(content) + + data_file = UploadFile(filename="abc.csv", file=uploaded_file) + buffered_file = BufferedFile(name="abc.csv", contents=content, path=None) + + decoy.when( + await file_reader_writer.read(files=[data_file]) # type: ignore[list-item] + ).then_return([buffered_file]) + decoy.when(await file_hasher.hash(files=[buffered_file])).then_return("abc123") + decoy.when(data_files_store.get_file_info_by_hash("abc123")).then_return( + DataFileInfo( + id="existing-file-id", + name="abc.csv", + file_hash="abc123", + created_at=datetime(year=2023, month=6, day=18), + ) + ) + + result = await upload_data_file( + file=data_file, + file_path=None, + data_files_directory=data_files_directory, + data_files_store=data_files_store, + file_reader_writer=file_reader_writer, + file_hasher=file_hasher, + file_id="data-file-id", + created_at=datetime(year=2024, month=6, day=18), + ) + assert result.status_code == 200 + assert result.content.data == DataFile( + id="existing-file-id", + name="abc.csv", + createdAt=datetime(year=2023, month=6, day=18), + ) + + +async def test_upload_new_data_file_path( + decoy: Decoy, + data_files_store: DataFilesStore, + file_reader_writer: FileReaderWriter, + file_hasher: FileHasher, +) -> None: + """It should store the data file from path to persistent storage & update the database.""" + data_files_directory = Path("/dev/null") + content = bytes("some_content", encoding="utf-8") + buffered_file = BufferedFile(name="abc.csv", contents=content, path=None) + + decoy.when( + await file_reader_writer.read(files=[Path("/data/my_data_file.csv")]) + ).then_return([buffered_file]) + decoy.when(await file_hasher.hash(files=[buffered_file])).then_return("abc123") + decoy.when(data_files_store.get_file_info_by_hash("abc123")).then_return(None) + + result = await upload_data_file( + file=None, + file_path="/data/my_data_file.csv", + data_files_directory=data_files_directory, + data_files_store=data_files_store, + file_reader_writer=file_reader_writer, + file_hasher=file_hasher, + file_id="data-file-id", + created_at=datetime(year=2024, month=6, day=18), + ) + assert result.status_code == 201 + assert result.content.data == DataFile( + id="data-file-id", + name="abc.csv", + createdAt=datetime(year=2024, month=6, day=18), + ) + decoy.verify( + await file_reader_writer.write( + directory=data_files_directory / "data-file-id", files=[buffered_file] + ), + await data_files_store.insert( + DataFileInfo( + id="data-file-id", + name="abc.csv", + file_hash="abc123", + created_at=datetime(year=2024, month=6, day=18), + ) + ), + ) + + +async def test_upload_non_existent_file_path( + decoy: Decoy, + data_files_store: DataFilesStore, + file_reader_writer: FileReaderWriter, + file_hasher: FileHasher, +) -> None: + """It should store the data file from path to persistent storage & update the database.""" + data_files_directory = Path("/dev/null") + decoy.when( + await file_reader_writer.read(files=[Path("/data/my_data_file.csv")]) + ).then_raise(FileNotFoundError("Uh oh!")) + + with pytest.raises(ApiError) as exc_info: + await upload_data_file( + file=None, + file_path="/data/my_data_file.csv", + data_files_directory=data_files_directory, + data_files_store=data_files_store, + file_reader_writer=file_reader_writer, + file_hasher=file_hasher, + file_id="data-file-id", + created_at=datetime(year=2024, month=6, day=18), + ) + assert exc_info.value.status_code == 404 + assert exc_info.value.content["errors"][0]["id"] == "FileNotFound" + + +async def test_upload_non_csv_file( + decoy: Decoy, + data_files_store: DataFilesStore, + file_reader_writer: FileReaderWriter, + file_hasher: FileHasher, +) -> None: + """It should store the data file from path to persistent storage & update the database.""" + data_files_directory = Path("/dev/null") + content = bytes("some_content", encoding="utf-8") + buffered_file = BufferedFile(name="abc.png", contents=content, path=None) + + decoy.when( + await file_reader_writer.read(files=[Path("/data/my_data_file.csv")]) + ).then_return([buffered_file]) + with pytest.raises(ApiError) as exc_info: + await upload_data_file( + file=None, + file_path="/data/my_data_file.csv", + data_files_directory=data_files_directory, + data_files_store=data_files_store, + file_reader_writer=file_reader_writer, + file_hasher=file_hasher, + file_id="data-file-id", + created_at=datetime(year=2024, month=6, day=18), + ) + assert exc_info.value.status_code == 422 + assert exc_info.value.content["errors"][0]["id"] == "UnexpectedFileFormat" diff --git a/robot-server/tests/integration/data_files/color_codes.csv b/robot-server/tests/integration/data_files/color_codes.csv new file mode 100644 index 00000000000..4d4259d56b7 --- /dev/null +++ b/robot-server/tests/integration/data_files/color_codes.csv @@ -0,0 +1,17 @@ +Name,HEX,RGB +White,#FFFFFF,"rgb(100,100,100)" +Silver,#C0C0C0,"rgb(75,75,75)" +Gray,#808080,"rgb(50,50,50)" +Black,#000000,"rgb(0,0,0)" +Red,#FF0000,"rgb(100,0,0)" +Maroon,#800000,"rgb(50,0,0)" +Yellow,#FFFF00,"rgb(100,100,0)" +Olive,#808000,"rgb(50,50,0)" +Lime,#00FF00,"rgb(0,100,0)" +Green,#008000,"rgb(0,50,0)" +Aqua,#00FFFF,"rgb(0,100,100)" +Teal,#008080,"rgb(0,50,50)" +Blue,#0000FF,"rgb(0,0,100)" +Navy,#000080,"rgb(0,0,50)" +Fuchsia,#FF00FF,"rgb(100,0,100)" +Purple,#800080,"rgb(50,0,50)" \ No newline at end of file diff --git a/robot-server/tests/integration/data_files/sample_record.csv b/robot-server/tests/integration/data_files/sample_record.csv new file mode 100644 index 00000000000..a6f2327ac25 --- /dev/null +++ b/robot-server/tests/integration/data_files/sample_record.csv @@ -0,0 +1,8 @@ +Employee name,Title,Department +Arthur Weasley,Head of Office,Detection and Confiscation of Counterfeit Defensive Spells and Protective Objects +Kingsley Shacklebolt,Auror,Auror Office +Mafalda Hopkirk,Assistant,Improper Use of Magic Office +Corban Yaxley,Head of Department,Department of Magical Law Enforcement +Walden Macnair,Executioner,Committee for the Disposal of Dangerous Creatures +Broderick Bode,Unspeakable,Department of Mysteries +Wilkie Twycross,Apparition Instructor,Unknown \ No newline at end of file diff --git a/robot-server/tests/integration/dev_server.py b/robot-server/tests/integration/dev_server.py index f549a4752e8..0a72319f746 100644 --- a/robot-server/tests/integration/dev_server.py +++ b/robot-server/tests/integration/dev_server.py @@ -32,7 +32,6 @@ def __init__( if persistence_directory is not None else Path(tempfile.mkdtemp()) ) - self.maximum_runs = maximum_runs self.maximum_unused_protocols = maximum_unused_protocols diff --git a/robot-server/tests/integration/http_api/data_files/__init__.py b/robot-server/tests/integration/http_api/data_files/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/robot-server/tests/integration/http_api/data_files/test_upload_data_file.tavern.yaml b/robot-server/tests/integration/http_api/data_files/test_upload_data_file.tavern.yaml new file mode 100644 index 00000000000..9d13807624b --- /dev/null +++ b/robot-server/tests/integration/http_api/data_files/test_upload_data_file.tavern.yaml @@ -0,0 +1,50 @@ +test_name: Upload a data file to the server + +marks: + - usefixtures: + - ot2_server_base_url +stages: + - name: Upload color_codes.csv file + request: + url: '{ot2_server_base_url}/dataFiles' + method: POST + files: + file: 'tests/integration/data_files/color_codes.csv' + response: + save: + json: + file_info: data + status_code: 201 + json: + data: + id: !anystr + name: "color_codes.csv" + createdAt: !re_fullmatch "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d+(Z|([+-]\\d{2}:\\d{2}))" + + - name: Upload same file again. It should not create a new record. + request: + url: '{ot2_server_base_url}/dataFiles' + method: POST + files: + file: 'tests/integration/data_files/color_codes.csv' + response: + status_code: 200 + json: + data: !force_original_structure '{file_info}' + + - name: Upload color_codes_2.csv file using file path + request: + url: '{ot2_server_base_url}/dataFiles' + method: POST + data: + filePath: 'tests/integration/data_files/sample_record.csv' + response: + save: + json: + file_info: data + status_code: 201 + json: + data: + id: !anystr + name: "sample_record.csv" + createdAt: !re_fullmatch "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d+(Z|([+-]\\d{2}:\\d{2}))" diff --git a/robot-server/tests/integration/protocol_files.py b/robot-server/tests/integration/protocol_files.py index 25df1a5b5b3..de90610610b 100644 --- a/robot-server/tests/integration/protocol_files.py +++ b/robot-server/tests/integration/protocol_files.py @@ -8,7 +8,7 @@ def get_protocol( protocol_name: str, protocol_extension: Literal[".py", ".json"] ) -> str: - """A NamedTemporaryFile valid json protocol.""" + """A NamedTemporaryFile valid python/ json protocol.""" contents = "" with open(Path(f"./tests/integration/protocols/simple{protocol_extension}")) as f: contents = f.read() @@ -19,7 +19,7 @@ def get_protocol( def get_json_protocol(protocol_name: str) -> IO[bytes]: - """A NamedTemporaryFile valid python protocol.""" + """A NamedTemporaryFile valid JSON protocol.""" return create_temp_file(".json", get_protocol(protocol_name, ".json")) diff --git a/shared-data/command/schemas/8.json b/shared-data/command/schemas/8.json index 586174612a1..d6fb78e12c3 100644 --- a/shared-data/command/schemas/8.json +++ b/shared-data/command/schemas/8.json @@ -1657,6 +1657,11 @@ "title": "Tipoverlapnotafterversion", "description": "A version of tip overlap data to not exceed. The highest-versioned tip overlap data that does not exceed this version will be used. Versions are expressed as vN where N is an integer, counting up from v0. If None, the current highest version will be used.", "type": "string" + }, + "liquidPresenceDetection": { + "title": "Liquidpresencedetection", + "description": "Enable liquid presence detection for this pipette. Defaults to False.", + "type": "boolean" } }, "required": ["pipetteName", "mount"] diff --git a/shared-data/js/__tests__/pipettes.test.ts b/shared-data/js/__tests__/pipettes.test.ts index 6dc9b36ec9c..123def5a7d3 100644 --- a/shared-data/js/__tests__/pipettes.test.ts +++ b/shared-data/js/__tests__/pipettes.test.ts @@ -149,6 +149,20 @@ describe('pipette data accessors', () => { backLeftCorner: [-8, -22, -259.15], frontRightCorner: [-8, -22, -259.15], }, + lldSettings: { + t50: { + minHeight: 0.5, + minVolume: 0, + }, + t200: { + minHeight: 0.5, + minVolume: 0, + }, + t1000: { + minHeight: 0.5, + minVolume: 0, + }, + }, } as PipetteV2Specs expect(getPipetteSpecsV2('p1000_single_flex')).toStrictEqual( mockP1000Specs diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_0.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_0.json index 10b93dfcaaa..eca64bcc383 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_0.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_0.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_3.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_3.json index 10b93dfcaaa..eca64bcc383 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_3.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_3.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_4.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_4.json index 10b93dfcaaa..eca64bcc383 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_4.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_4.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_5.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_5.json index 10b93dfcaaa..eca64bcc383 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_5.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_5.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_6.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_6.json index 10b93dfcaaa..eca64bcc383 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_6.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p10/1_6.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/1_0.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/1_0.json index bc0e3637326..d598b8b289e 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/1_0.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/1_0.json @@ -37,5 +37,6 @@ "F1": [-8.0, -61.0, -259.15], "G1": [-8.0, -70.0, -259.15], "H1": [-8.0, -79.0, -259.15] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_0.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_0.json index bc0e3637326..bc76a2669d5 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_0.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_0.json @@ -37,5 +37,19 @@ "F1": [-8.0, -61.0, -259.15], "G1": [-8.0, -70.0, -259.15], "H1": [-8.0, -79.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_3.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_3.json index bc0e3637326..bc76a2669d5 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_3.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_3.json @@ -37,5 +37,19 @@ "F1": [-8.0, -61.0, -259.15], "G1": [-8.0, -70.0, -259.15], "H1": [-8.0, -79.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_4.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_4.json index bc0e3637326..bc76a2669d5 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_4.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_4.json @@ -37,5 +37,19 @@ "F1": [-8.0, -61.0, -259.15], "G1": [-8.0, -70.0, -259.15], "H1": [-8.0, -79.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_5.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_5.json index bc0e3637326..bc76a2669d5 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_5.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p1000/3_5.json @@ -37,5 +37,19 @@ "F1": [-8.0, -61.0, -259.15], "G1": [-8.0, -70.0, -259.15], "H1": [-8.0, -79.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p20/2_0.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p20/2_0.json index e416638c4dc..4a4b2e008ec 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p20/2_0.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p20/2_0.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 19.4], "G1": [0.0, -22.5, 19.4], "H1": [0.0, -31.5, 19.4] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p20/2_1.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p20/2_1.json index e416638c4dc..4a4b2e008ec 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p20/2_1.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p20/2_1.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 19.4], "G1": [0.0, -22.5, 19.4], "H1": [0.0, -31.5, 19.4] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_0.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_0.json index b888f3138b0..ef55316f83d 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_0.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_0.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_3.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_3.json index b888f3138b0..ef55316f83d 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_3.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_3.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_4.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_4.json index b888f3138b0..ef55316f83d 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_4.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_4.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_5.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_5.json index b888f3138b0..ef55316f83d 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_5.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/1_5.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/2_0.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/2_0.json index 713a907d1d1..c5997e31b6d 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/2_0.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/2_0.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 35.52], "G1": [0.0, -22.5, 35.52], "H1": [0.0, -31.5, 35.52] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/2_1.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/2_1.json index 713a907d1d1..c5997e31b6d 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p300/2_1.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p300/2_1.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 35.52], "G1": [0.0, -22.5, 35.52], "H1": [0.0, -31.5, 35.52] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_0.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_0.json index 691af1c0d2c..9d40fce93ea 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_0.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_0.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_3.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_3.json index 691af1c0d2c..9d40fce93ea 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_3.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_3.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_4.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_4.json index 691af1c0d2c..9d40fce93ea 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_4.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_4.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_5.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_5.json index 691af1c0d2c..9d40fce93ea 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_5.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/1_5.json @@ -37,5 +37,6 @@ "F1": [0.0, -13.5, 0.8], "G1": [0.0, -22.5, 0.8], "H1": [0.0, -31.5, 0.8] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_0.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_0.json index fbda0a2ede3..9bbd489e296 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_0.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_0.json @@ -37,5 +37,11 @@ "F1": [-8.0, -61.0, -259.15], "G1": [-8.0, -70.0, -259.15], "H1": [-8.0, -79.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_3.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_3.json index fbda0a2ede3..9bbd489e296 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_3.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_3.json @@ -37,5 +37,11 @@ "F1": [-8.0, -61.0, -259.15], "G1": [-8.0, -70.0, -259.15], "H1": [-8.0, -79.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_4.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_4.json index fbda0a2ede3..9bbd489e296 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_4.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_4.json @@ -37,5 +37,11 @@ "F1": [-8.0, -61.0, -259.15], "G1": [-8.0, -70.0, -259.15], "H1": [-8.0, -79.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_5.json b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_5.json index fbda0a2ede3..9bbd489e296 100644 --- a/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_5.json +++ b/shared-data/pipette/definitions/2/geometry/eight_channel/p50/3_5.json @@ -37,5 +37,11 @@ "F1": [-8.0, -61.0, -259.15], "G1": [-8.0, -70.0, -259.15], "H1": [-8.0, -79.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/1_0.json b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/1_0.json index da209a72907..8b8ea364999 100644 --- a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/1_0.json +++ b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/1_0.json @@ -291,5 +291,6 @@ "H10": [45.0, -88.5, -259.15], "H11": [54.0, -88.5, -259.15], "H12": [63.0, -88.5, -259.15] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_0.json b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_0.json index da209a72907..b0c41b25661 100644 --- a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_0.json +++ b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_0.json @@ -291,5 +291,19 @@ "H10": [45.0, -88.5, -259.15], "H11": [54.0, -88.5, -259.15], "H12": [63.0, -88.5, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_3.json b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_3.json index da209a72907..b0c41b25661 100644 --- a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_3.json +++ b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_3.json @@ -291,5 +291,19 @@ "H10": [45.0, -88.5, -259.15], "H11": [54.0, -88.5, -259.15], "H12": [63.0, -88.5, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_4.json b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_4.json index da209a72907..b0c41b25661 100644 --- a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_4.json +++ b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_4.json @@ -291,5 +291,19 @@ "H10": [45.0, -88.5, -259.15], "H11": [54.0, -88.5, -259.15], "H12": [63.0, -88.5, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_5.json b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_5.json index da209a72907..b0c41b25661 100644 --- a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_5.json +++ b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_5.json @@ -291,5 +291,19 @@ "H10": [45.0, -88.5, -259.15], "H11": [54.0, -88.5, -259.15], "H12": [63.0, -88.5, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_6.json b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_6.json index da209a72907..b0c41b25661 100644 --- a/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_6.json +++ b/shared-data/pipette/definitions/2/geometry/ninety_six_channel/p1000/3_6.json @@ -291,5 +291,19 @@ "H10": [45.0, -88.5, -259.15], "H11": [54.0, -88.5, -259.15], "H12": [63.0, -88.5, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_0.json b/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_0.json index b85709722a0..cb47066c4b9 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_0.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_0.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 12.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_3.json b/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_3.json index b85709722a0..cb47066c4b9 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_3.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_3.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 12.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_4.json b/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_4.json index b85709722a0..cb47066c4b9 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_4.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_4.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 12.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_5.json b/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_5.json index b85709722a0..cb47066c4b9 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_5.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p10/1_5.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 12.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_0.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_0.json index 0260e1b2c7a..a072c322a4b 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_0.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_0.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 45.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_3.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_3.json index 0260e1b2c7a..a072c322a4b 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_3.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_3.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 45.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_4.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_4.json index 0260e1b2c7a..a072c322a4b 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_4.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_4.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 45.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_5.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_5.json index 0260e1b2c7a..a072c322a4b 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_5.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/1_5.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 45.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_0.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_0.json index 1d8f41607bf..ccb2a50b97b 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_0.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_0.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 50.14] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_1.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_1.json index 1d8f41607bf..ccb2a50b97b 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_1.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_1.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 50.14] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_2.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_2.json index 1d8f41607bf..ccb2a50b97b 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_2.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/2_2.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 50.14] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_0.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_0.json index a1b2135a278..b30d15ddb66 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_0.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_0.json @@ -10,5 +10,19 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_3.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_3.json index a1b2135a278..b30d15ddb66 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_3.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_3.json @@ -10,5 +10,19 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_4.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_4.json index a1b2135a278..b30d15ddb66 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_4.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_4.json @@ -10,5 +10,19 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_5.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_5.json index a1b2135a278..b30d15ddb66 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_5.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_5.json @@ -10,5 +10,19 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_6.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_6.json index a1b2135a278..b30d15ddb66 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_6.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_6.json @@ -10,5 +10,19 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_7.json b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_7.json index a1b2135a278..b30d15ddb66 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_7.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p1000/3_7.json @@ -10,5 +10,19 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t200": { + "minHeight": 0.5, + "minVolume": 0 + }, + "t1000": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_0.json b/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_0.json index 5f34bea0e3c..2bd492481db 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_0.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_0.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 10.45] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_1.json b/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_1.json index 5f34bea0e3c..2bd492481db 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_1.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_1.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 10.45] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_2.json b/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_2.json index 5f34bea0e3c..2bd492481db 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_2.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p20/2_2.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 10.45] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_0.json b/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_0.json index c800c98b9bb..2baf8679f7c 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_0.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_0.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 25.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_3.json b/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_3.json index c800c98b9bb..2baf8679f7c 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_3.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_3.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 25.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_4.json b/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_4.json index c800c98b9bb..2baf8679f7c 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_4.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_4.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 25.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_5.json b/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_5.json index c800c98b9bb..2baf8679f7c 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_5.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p300/1_5.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 25.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p300/2_0.json b/shared-data/pipette/definitions/2/geometry/single_channel/p300/2_0.json index 0068dc51390..3c8c92499a2 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p300/2_0.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p300/2_0.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 29.45] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p300/2_1.json b/shared-data/pipette/definitions/2/geometry/single_channel/p300/2_1.json index 0068dc51390..3c8c92499a2 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p300/2_1.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p300/2_1.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 29.45] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_0.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_0.json index 85bc6700fd3..9aadf814228 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_0.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_0.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 25.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_3.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_3.json index 85bc6700fd3..9aadf814228 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_3.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_3.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 25.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_4.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_4.json index 85bc6700fd3..9aadf814228 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_4.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_4.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 25.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_5.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_5.json index 85bc6700fd3..9aadf814228 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_5.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p50/1_5.json @@ -10,5 +10,6 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [0.0, 0.0, 25.0] - } + }, + "lldSettings": {} } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_0.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_0.json index ca5180c4415..b40f6957277 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_0.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_0.json @@ -10,5 +10,11 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_3.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_3.json index ca5180c4415..b40f6957277 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_3.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_3.json @@ -10,5 +10,11 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_4.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_4.json index ca5180c4415..b40f6957277 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_4.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_4.json @@ -10,5 +10,11 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_5.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_5.json index ca5180c4415..b40f6957277 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_5.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_5.json @@ -10,5 +10,11 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_6.json b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_6.json index ca5180c4415..b40f6957277 100644 --- a/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_6.json +++ b/shared-data/pipette/definitions/2/geometry/single_channel/p50/3_6.json @@ -10,5 +10,11 @@ "orderedColumns": [{ "key": "1", "orderedNozzles": ["A1"] }], "nozzleMap": { "A1": [-8.0, -22.0, -259.15] + }, + "lldSettings": { + "t50": { + "minHeight": 0.5, + "minVolume": 0 + } } } diff --git a/shared-data/pipette/schemas/2/pipetteGeometrySchema.json b/shared-data/pipette/schemas/2/pipetteGeometrySchema.json index 0591d3261a6..a9a263b45f5 100644 --- a/shared-data/pipette/schemas/2/pipetteGeometrySchema.json +++ b/shared-data/pipette/schemas/2/pipetteGeometrySchema.json @@ -84,6 +84,34 @@ "patternProperties": { "[A-Z]+[0-9]+": { "$ref": "#/definitions/xyzArray" } } + }, + "lldSettings": { + "type": "object", + "description": "Minimum space requirements for Liquid Level Detection to work properly", + "additionalProperties": false, + "properties": { + "t50": { + "type": "object", + "properties": { + "minHeight": { "type": "number" }, + "minVolume": { "type": "number" } + } + }, + "t200": { + "type": "object", + "properties": { + "minHeight": { "type": "number" }, + "minVolume": { "type": "number" } + } + }, + "t1000": { + "type": "object", + "properties": { + "minHeight": { "type": "number" }, + "minVolume": { "type": "number" } + } + } + } } } } diff --git a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py index a46c668dc2c..61e8f94143e 100644 --- a/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py +++ b/shared-data/python/opentrons_shared_data/pipette/pipette_definition.py @@ -358,7 +358,6 @@ class PipettePhysicalPropertiesDefinition(BaseModel): description="The distance the high throughput tip motors will travel to check tip status.", alias="tipPresenceCheckDistanceMM", ) - end_tip_action_retract_distance_mm: float = Field( default=0.0, description="The distance to move the head up after a tip drop or pickup.", @@ -440,6 +439,7 @@ class PipetteGeometryDefinition(BaseModel): ) ordered_columns: List[PipetteColumnDefinition] = Field(..., alias="orderedColumns") ordered_rows: List[PipetteRowDefinition] = Field(..., alias="orderedRows") + lld_settings: Dict[str, Dict[str, float]] = Field(..., alias="lldSettings") @validator("nozzle_map", pre=True) def check_nonempty_strings( diff --git a/step-generation/src/__tests__/__snapshots__/fixtureGeneration.test.ts.snap b/step-generation/src/__tests__/__snapshots__/fixtureGeneration.test.ts.snap index 93f545f568f..c4d02928884 100644 --- a/step-generation/src/__tests__/__snapshots__/fixtureGeneration.test.ts.snap +++ b/step-generation/src/__tests__/__snapshots__/fixtureGeneration.test.ts.snap @@ -9650,6 +9650,7 @@ exports[`snapshot tests > makeContext 1`] = ` }, }, }, + "lldSettings": {}, "model": "p1000", "nozzleMap": { "A1": [ @@ -12270,6 +12271,7 @@ exports[`snapshot tests > makeContext 1`] = ` }, }, }, + "lldSettings": {}, "model": "p10", "nozzleMap": { "A1": [ @@ -13907,6 +13909,7 @@ exports[`snapshot tests > makeContext 1`] = ` }, }, }, + "lldSettings": {}, "model": "p10", "nozzleMap": { "A1": [ @@ -15270,6 +15273,7 @@ exports[`snapshot tests > makeContext 1`] = ` }, }, }, + "lldSettings": {}, "model": "p300", "nozzleMap": { "A1": [ @@ -16999,6 +17003,7 @@ exports[`snapshot tests > makeContext 1`] = ` }, }, }, + "lldSettings": {}, "model": "p300", "nozzleMap": { "A1": [ diff --git a/test-data-generation/src/test_data_generation/python_protocol_generation/ast_helpers.py b/test-data-generation/src/test_data_generation/python_protocol_generation/ast_helpers.py index 510406e33f9..468a8fb8546 100644 --- a/test-data-generation/src/test_data_generation/python_protocol_generation/ast_helpers.py +++ b/test-data-generation/src/test_data_generation/python_protocol_generation/ast_helpers.py @@ -123,7 +123,7 @@ def load_module( @classmethod def load_instrument( - cls, instrument_name: str, mount: typing.Literal["left", "right"] + cls, instrument_name: str, mount: typing.Literal["left", "right"], ) -> "CallFunction": """Create a CallFunction for loading an instrument.""" return cls(