opentrons 8.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/__init__.py +150 -0
- opentrons/_version.py +34 -0
- opentrons/calibration_storage/__init__.py +54 -0
- opentrons/calibration_storage/deck_configuration.py +62 -0
- opentrons/calibration_storage/encoder_decoder.py +31 -0
- opentrons/calibration_storage/file_operators.py +142 -0
- opentrons/calibration_storage/helpers.py +103 -0
- opentrons/calibration_storage/ot2/__init__.py +34 -0
- opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
- opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
- opentrons/calibration_storage/ot2/models/__init__.py +0 -0
- opentrons/calibration_storage/ot2/models/v1.py +149 -0
- opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
- opentrons/calibration_storage/ot2/tip_length.py +281 -0
- opentrons/calibration_storage/ot3/__init__.py +31 -0
- opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
- opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
- opentrons/calibration_storage/ot3/models/__init__.py +0 -0
- opentrons/calibration_storage/ot3/models/v1.py +122 -0
- opentrons/calibration_storage/ot3/module_offset.py +138 -0
- opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
- opentrons/calibration_storage/types.py +45 -0
- opentrons/cli/__init__.py +21 -0
- opentrons/cli/__main__.py +5 -0
- opentrons/cli/analyze.py +557 -0
- opentrons/config/__init__.py +631 -0
- opentrons/config/advanced_settings.py +871 -0
- opentrons/config/defaults_ot2.py +214 -0
- opentrons/config/defaults_ot3.py +499 -0
- opentrons/config/feature_flags.py +86 -0
- opentrons/config/gripper_config.py +55 -0
- opentrons/config/reset.py +203 -0
- opentrons/config/robot_configs.py +187 -0
- opentrons/config/types.py +183 -0
- opentrons/drivers/__init__.py +0 -0
- opentrons/drivers/absorbance_reader/__init__.py +11 -0
- opentrons/drivers/absorbance_reader/abstract.py +72 -0
- opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
- opentrons/drivers/absorbance_reader/driver.py +81 -0
- opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
- opentrons/drivers/absorbance_reader/simulator.py +84 -0
- opentrons/drivers/asyncio/__init__.py +0 -0
- opentrons/drivers/asyncio/communication/__init__.py +22 -0
- opentrons/drivers/asyncio/communication/async_serial.py +187 -0
- opentrons/drivers/asyncio/communication/errors.py +88 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +557 -0
- opentrons/drivers/command_builder.py +102 -0
- opentrons/drivers/flex_stacker/__init__.py +13 -0
- opentrons/drivers/flex_stacker/abstract.py +214 -0
- opentrons/drivers/flex_stacker/driver.py +768 -0
- opentrons/drivers/flex_stacker/errors.py +68 -0
- opentrons/drivers/flex_stacker/simulator.py +309 -0
- opentrons/drivers/flex_stacker/types.py +367 -0
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/drivers/heater_shaker/__init__.py +5 -0
- opentrons/drivers/heater_shaker/abstract.py +76 -0
- opentrons/drivers/heater_shaker/driver.py +204 -0
- opentrons/drivers/heater_shaker/simulator.py +94 -0
- opentrons/drivers/mag_deck/__init__.py +6 -0
- opentrons/drivers/mag_deck/abstract.py +44 -0
- opentrons/drivers/mag_deck/driver.py +208 -0
- opentrons/drivers/mag_deck/simulator.py +63 -0
- opentrons/drivers/rpi_drivers/__init__.py +33 -0
- opentrons/drivers/rpi_drivers/dev_types.py +94 -0
- opentrons/drivers/rpi_drivers/gpio.py +282 -0
- opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
- opentrons/drivers/rpi_drivers/interfaces.py +15 -0
- opentrons/drivers/rpi_drivers/types.py +364 -0
- opentrons/drivers/rpi_drivers/usb.py +102 -0
- opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
- opentrons/drivers/serial_communication.py +151 -0
- opentrons/drivers/smoothie_drivers/__init__.py +4 -0
- opentrons/drivers/smoothie_drivers/connection.py +51 -0
- opentrons/drivers/smoothie_drivers/constants.py +121 -0
- opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
- opentrons/drivers/smoothie_drivers/errors.py +49 -0
- opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
- opentrons/drivers/smoothie_drivers/simulator.py +99 -0
- opentrons/drivers/smoothie_drivers/types.py +16 -0
- opentrons/drivers/temp_deck/__init__.py +10 -0
- opentrons/drivers/temp_deck/abstract.py +54 -0
- opentrons/drivers/temp_deck/driver.py +197 -0
- opentrons/drivers/temp_deck/simulator.py +57 -0
- opentrons/drivers/thermocycler/__init__.py +12 -0
- opentrons/drivers/thermocycler/abstract.py +99 -0
- opentrons/drivers/thermocycler/driver.py +395 -0
- opentrons/drivers/thermocycler/simulator.py +126 -0
- opentrons/drivers/types.py +107 -0
- opentrons/drivers/utils.py +222 -0
- opentrons/execute.py +742 -0
- opentrons/hardware_control/__init__.py +65 -0
- opentrons/hardware_control/__main__.py +77 -0
- opentrons/hardware_control/adapters.py +98 -0
- opentrons/hardware_control/api.py +1347 -0
- opentrons/hardware_control/backends/__init__.py +7 -0
- opentrons/hardware_control/backends/controller.py +400 -0
- opentrons/hardware_control/backends/errors.py +9 -0
- opentrons/hardware_control/backends/estop_state.py +164 -0
- opentrons/hardware_control/backends/flex_protocol.py +497 -0
- opentrons/hardware_control/backends/ot3controller.py +1930 -0
- opentrons/hardware_control/backends/ot3simulator.py +900 -0
- opentrons/hardware_control/backends/ot3utils.py +664 -0
- opentrons/hardware_control/backends/simulator.py +442 -0
- opentrons/hardware_control/backends/status_bar_state.py +240 -0
- opentrons/hardware_control/backends/subsystem_manager.py +431 -0
- opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
- opentrons/hardware_control/backends/types.py +14 -0
- opentrons/hardware_control/constants.py +6 -0
- opentrons/hardware_control/dev_types.py +125 -0
- opentrons/hardware_control/emulation/__init__.py +0 -0
- opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
- opentrons/hardware_control/emulation/app.py +56 -0
- opentrons/hardware_control/emulation/connection_handler.py +38 -0
- opentrons/hardware_control/emulation/heater_shaker.py +150 -0
- opentrons/hardware_control/emulation/magdeck.py +60 -0
- opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
- opentrons/hardware_control/emulation/module_server/client.py +78 -0
- opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
- opentrons/hardware_control/emulation/module_server/models.py +31 -0
- opentrons/hardware_control/emulation/module_server/server.py +110 -0
- opentrons/hardware_control/emulation/parser.py +74 -0
- opentrons/hardware_control/emulation/proxy.py +241 -0
- opentrons/hardware_control/emulation/run_emulator.py +68 -0
- opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
- opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
- opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
- opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
- opentrons/hardware_control/emulation/settings.py +119 -0
- opentrons/hardware_control/emulation/simulations.py +133 -0
- opentrons/hardware_control/emulation/smoothie.py +192 -0
- opentrons/hardware_control/emulation/tempdeck.py +69 -0
- opentrons/hardware_control/emulation/thermocycler.py +128 -0
- opentrons/hardware_control/emulation/types.py +10 -0
- opentrons/hardware_control/emulation/util.py +38 -0
- opentrons/hardware_control/errors.py +43 -0
- opentrons/hardware_control/execution_manager.py +164 -0
- opentrons/hardware_control/instruments/__init__.py +5 -0
- opentrons/hardware_control/instruments/instrument_abc.py +39 -0
- opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
- opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
- opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
- opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
- opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
- opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
- opentrons/hardware_control/module_control.py +332 -0
- opentrons/hardware_control/modules/__init__.py +69 -0
- opentrons/hardware_control/modules/absorbance_reader.py +373 -0
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/flex_stacker.py +948 -0
- opentrons/hardware_control/modules/heater_shaker.py +426 -0
- opentrons/hardware_control/modules/lid_temp_status.py +35 -0
- opentrons/hardware_control/modules/magdeck.py +233 -0
- opentrons/hardware_control/modules/mod_abc.py +245 -0
- opentrons/hardware_control/modules/module_calibration.py +93 -0
- opentrons/hardware_control/modules/plate_temp_status.py +61 -0
- opentrons/hardware_control/modules/tempdeck.py +299 -0
- opentrons/hardware_control/modules/thermocycler.py +731 -0
- opentrons/hardware_control/modules/types.py +417 -0
- opentrons/hardware_control/modules/update.py +255 -0
- opentrons/hardware_control/modules/utils.py +73 -0
- opentrons/hardware_control/motion_utilities.py +318 -0
- opentrons/hardware_control/nozzle_manager.py +422 -0
- opentrons/hardware_control/ot3_calibration.py +1171 -0
- opentrons/hardware_control/ot3api.py +3227 -0
- opentrons/hardware_control/pause_manager.py +31 -0
- opentrons/hardware_control/poller.py +112 -0
- opentrons/hardware_control/protocols/__init__.py +106 -0
- opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
- opentrons/hardware_control/protocols/calibratable.py +45 -0
- opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
- opentrons/hardware_control/protocols/configurable.py +48 -0
- opentrons/hardware_control/protocols/event_sourcer.py +18 -0
- opentrons/hardware_control/protocols/execution_controllable.py +33 -0
- opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
- opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
- opentrons/hardware_control/protocols/gripper_controller.py +55 -0
- opentrons/hardware_control/protocols/hardware_manager.py +51 -0
- opentrons/hardware_control/protocols/identifiable.py +16 -0
- opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
- opentrons/hardware_control/protocols/liquid_handler.py +266 -0
- opentrons/hardware_control/protocols/module_provider.py +16 -0
- opentrons/hardware_control/protocols/motion_controller.py +243 -0
- opentrons/hardware_control/protocols/position_estimator.py +45 -0
- opentrons/hardware_control/protocols/simulatable.py +10 -0
- opentrons/hardware_control/protocols/stoppable.py +9 -0
- opentrons/hardware_control/protocols/types.py +27 -0
- opentrons/hardware_control/robot_calibration.py +224 -0
- opentrons/hardware_control/scripts/README.md +28 -0
- opentrons/hardware_control/scripts/__init__.py +1 -0
- opentrons/hardware_control/scripts/gripper_control.py +208 -0
- opentrons/hardware_control/scripts/ot3gripper +7 -0
- opentrons/hardware_control/scripts/ot3repl +7 -0
- opentrons/hardware_control/scripts/repl.py +187 -0
- opentrons/hardware_control/scripts/tc_control.py +97 -0
- opentrons/hardware_control/scripts/update_module_fw.py +274 -0
- opentrons/hardware_control/simulator_setup.py +260 -0
- opentrons/hardware_control/thread_manager.py +431 -0
- opentrons/hardware_control/threaded_async_lock.py +97 -0
- opentrons/hardware_control/types.py +792 -0
- opentrons/hardware_control/util.py +234 -0
- opentrons/legacy_broker.py +53 -0
- opentrons/legacy_commands/__init__.py +1 -0
- opentrons/legacy_commands/commands.py +483 -0
- opentrons/legacy_commands/helpers.py +153 -0
- opentrons/legacy_commands/module_commands.py +276 -0
- opentrons/legacy_commands/protocol_commands.py +54 -0
- opentrons/legacy_commands/publisher.py +155 -0
- opentrons/legacy_commands/robot_commands.py +51 -0
- opentrons/legacy_commands/types.py +1186 -0
- opentrons/motion_planning/__init__.py +32 -0
- opentrons/motion_planning/adjacent_slots_getters.py +168 -0
- opentrons/motion_planning/deck_conflict.py +501 -0
- opentrons/motion_planning/errors.py +35 -0
- opentrons/motion_planning/types.py +42 -0
- opentrons/motion_planning/waypoints.py +218 -0
- opentrons/ordered_set.py +138 -0
- opentrons/protocol_api/__init__.py +105 -0
- opentrons/protocol_api/_liquid.py +157 -0
- opentrons/protocol_api/_liquid_properties.py +814 -0
- opentrons/protocol_api/_nozzle_layout.py +31 -0
- opentrons/protocol_api/_parameter_context.py +300 -0
- opentrons/protocol_api/_parameters.py +31 -0
- opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
- opentrons/protocol_api/_types.py +43 -0
- opentrons/protocol_api/config.py +23 -0
- opentrons/protocol_api/core/__init__.py +23 -0
- opentrons/protocol_api/core/common.py +33 -0
- opentrons/protocol_api/core/core_map.py +74 -0
- opentrons/protocol_api/core/engine/__init__.py +22 -0
- opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
- opentrons/protocol_api/core/engine/deck_conflict.py +400 -0
- opentrons/protocol_api/core/engine/exceptions.py +19 -0
- opentrons/protocol_api/core/engine/instrument.py +2391 -0
- opentrons/protocol_api/core/engine/labware.py +238 -0
- opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
- opentrons/protocol_api/core/engine/module_core.py +1027 -0
- opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
- opentrons/protocol_api/core/engine/point_calculations.py +64 -0
- opentrons/protocol_api/core/engine/protocol.py +1153 -0
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/stringify.py +74 -0
- opentrons/protocol_api/core/engine/transfer_components_executor.py +1006 -0
- opentrons/protocol_api/core/engine/well.py +241 -0
- opentrons/protocol_api/core/instrument.py +459 -0
- opentrons/protocol_api/core/labware.py +151 -0
- opentrons/protocol_api/core/legacy/__init__.py +11 -0
- opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
- opentrons/protocol_api/core/legacy/deck.py +369 -0
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
- opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
- opentrons/protocol_api/core/legacy/load_info.py +67 -0
- opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
- opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
- opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
- opentrons/protocol_api/core/module.py +484 -0
- opentrons/protocol_api/core/protocol.py +311 -0
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/core/well.py +116 -0
- opentrons/protocol_api/core/well_grid.py +45 -0
- opentrons/protocol_api/create_protocol_context.py +177 -0
- opentrons/protocol_api/deck.py +223 -0
- opentrons/protocol_api/disposal_locations.py +244 -0
- opentrons/protocol_api/instrument_context.py +3272 -0
- opentrons/protocol_api/labware.py +1579 -0
- opentrons/protocol_api/module_contexts.py +1447 -0
- opentrons/protocol_api/module_validation_and_errors.py +61 -0
- opentrons/protocol_api/protocol_context.py +1688 -0
- opentrons/protocol_api/robot_context.py +303 -0
- opentrons/protocol_api/validation.py +761 -0
- opentrons/protocol_engine/__init__.py +155 -0
- opentrons/protocol_engine/actions/__init__.py +65 -0
- opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
- opentrons/protocol_engine/actions/action_handler.py +13 -0
- opentrons/protocol_engine/actions/actions.py +302 -0
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/__init__.py +5 -0
- opentrons/protocol_engine/clients/sync_client.py +174 -0
- opentrons/protocol_engine/clients/transports.py +197 -0
- opentrons/protocol_engine/commands/__init__.py +757 -0
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
- opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
- opentrons/protocol_engine/commands/aspirate.py +244 -0
- opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
- opentrons/protocol_engine/commands/blow_out.py +146 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
- opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
- opentrons/protocol_engine/commands/command.py +308 -0
- opentrons/protocol_engine/commands/command_unions.py +974 -0
- opentrons/protocol_engine/commands/comment.py +57 -0
- opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
- opentrons/protocol_engine/commands/custom.py +67 -0
- opentrons/protocol_engine/commands/dispense.py +194 -0
- opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +232 -0
- opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +339 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
- opentrons/protocol_engine/commands/hash_command_params.py +38 -0
- opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
- opentrons/protocol_engine/commands/home.py +100 -0
- opentrons/protocol_engine/commands/identify_module.py +86 -0
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +464 -0
- opentrons/protocol_engine/commands/load_labware.py +210 -0
- opentrons/protocol_engine/commands/load_lid.py +154 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
- opentrons/protocol_engine/commands/load_liquid.py +95 -0
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +223 -0
- opentrons/protocol_engine/commands/load_pipette.py +167 -0
- opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
- opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
- opentrons/protocol_engine/commands/move_labware.py +546 -0
- opentrons/protocol_engine/commands/move_relative.py +102 -0
- opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
- opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
- opentrons/protocol_engine/commands/move_to_well.py +119 -0
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
- opentrons/protocol_engine/commands/pipetting_common.py +443 -0
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
- opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
- opentrons/protocol_engine/commands/reload_labware.py +90 -0
- opentrons/protocol_engine/commands/retract_axis.py +75 -0
- opentrons/protocol_engine/commands/robot/__init__.py +70 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
- opentrons/protocol_engine/commands/robot/common.py +18 -0
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
- opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
- opentrons/protocol_engine/commands/robot/move_to.py +94 -0
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
- opentrons/protocol_engine/commands/save_position.py +109 -0
- opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
- opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
- opentrons/protocol_engine/commands/set_status_bar.py +89 -0
- opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
- opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
- opentrons/protocol_engine/commands/touch_tip.py +189 -0
- opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
- opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
- opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
- opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
- opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
- opentrons/protocol_engine/create_protocol_engine.py +193 -0
- opentrons/protocol_engine/engine_support.py +28 -0
- opentrons/protocol_engine/error_recovery_policy.py +81 -0
- opentrons/protocol_engine/errors/__init__.py +191 -0
- opentrons/protocol_engine/errors/error_occurrence.py +182 -0
- opentrons/protocol_engine/errors/exceptions.py +1308 -0
- opentrons/protocol_engine/execution/__init__.py +50 -0
- opentrons/protocol_engine/execution/command_executor.py +216 -0
- opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
- opentrons/protocol_engine/execution/door_watcher.py +119 -0
- opentrons/protocol_engine/execution/equipment.py +819 -0
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +686 -0
- opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
- opentrons/protocol_engine/execution/labware_movement.py +297 -0
- opentrons/protocol_engine/execution/movement.py +350 -0
- opentrons/protocol_engine/execution/pipetting.py +607 -0
- opentrons/protocol_engine/execution/queue_worker.py +86 -0
- opentrons/protocol_engine/execution/rail_lights.py +25 -0
- opentrons/protocol_engine/execution/run_control.py +33 -0
- opentrons/protocol_engine/execution/status_bar.py +34 -0
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
- opentrons/protocol_engine/execution/tip_handler.py +550 -0
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/notes/__init__.py +17 -0
- opentrons/protocol_engine/notes/notes.py +59 -0
- opentrons/protocol_engine/plugins.py +104 -0
- opentrons/protocol_engine/protocol_engine.py +683 -0
- opentrons/protocol_engine/resources/__init__.py +26 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +68 -0
- opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
- opentrons/protocol_engine/resources/labware_validation.py +73 -0
- opentrons/protocol_engine/resources/model_utils.py +32 -0
- opentrons/protocol_engine/resources/module_data_provider.py +44 -0
- opentrons/protocol_engine/resources/ot3_validation.py +21 -0
- opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
- opentrons/protocol_engine/slot_standardization.py +128 -0
- opentrons/protocol_engine/state/__init__.py +1 -0
- opentrons/protocol_engine/state/_abstract_store.py +27 -0
- opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
- opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
- opentrons/protocol_engine/state/_move_types.py +83 -0
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +699 -0
- opentrons/protocol_engine/state/command_history.py +309 -0
- opentrons/protocol_engine/state/commands.py +1164 -0
- opentrons/protocol_engine/state/config.py +39 -0
- opentrons/protocol_engine/state/files.py +57 -0
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/geometry.py +2408 -0
- opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
- opentrons/protocol_engine/state/labware.py +1432 -0
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +73 -0
- opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
- opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
- opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
- opentrons/protocol_engine/state/modules.py +1515 -0
- opentrons/protocol_engine/state/motion.py +373 -0
- opentrons/protocol_engine/state/pipettes.py +905 -0
- opentrons/protocol_engine/state/state.py +421 -0
- opentrons/protocol_engine/state/state_summary.py +36 -0
- opentrons/protocol_engine/state/tips.py +420 -0
- opentrons/protocol_engine/state/update_types.py +904 -0
- opentrons/protocol_engine/state/wells.py +290 -0
- opentrons/protocol_engine/types/__init__.py +310 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +81 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +131 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +310 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +124 -0
- opentrons/protocol_reader/__init__.py +37 -0
- opentrons/protocol_reader/extract_labware_definitions.py +66 -0
- opentrons/protocol_reader/file_format_validator.py +152 -0
- opentrons/protocol_reader/file_hasher.py +27 -0
- opentrons/protocol_reader/file_identifier.py +284 -0
- opentrons/protocol_reader/file_reader_writer.py +90 -0
- opentrons/protocol_reader/input_file.py +16 -0
- opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
- opentrons/protocol_reader/protocol_reader.py +188 -0
- opentrons/protocol_reader/protocol_source.py +124 -0
- opentrons/protocol_reader/role_analyzer.py +86 -0
- opentrons/protocol_runner/__init__.py +26 -0
- opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
- opentrons/protocol_runner/json_file_reader.py +55 -0
- opentrons/protocol_runner/json_translator.py +314 -0
- opentrons/protocol_runner/legacy_command_mapper.py +852 -0
- opentrons/protocol_runner/legacy_context_plugin.py +116 -0
- opentrons/protocol_runner/protocol_runner.py +530 -0
- opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
- opentrons/protocol_runner/run_orchestrator.py +496 -0
- opentrons/protocol_runner/task_queue.py +95 -0
- opentrons/protocols/__init__.py +6 -0
- opentrons/protocols/advanced_control/__init__.py +0 -0
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +60 -0
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +180 -0
- opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
- opentrons/protocols/api_support/__init__.py +0 -0
- opentrons/protocols/api_support/constants.py +8 -0
- opentrons/protocols/api_support/deck_type.py +110 -0
- opentrons/protocols/api_support/definitions.py +18 -0
- opentrons/protocols/api_support/instrument.py +151 -0
- opentrons/protocols/api_support/labware_like.py +233 -0
- opentrons/protocols/api_support/tip_tracker.py +175 -0
- opentrons/protocols/api_support/types.py +32 -0
- opentrons/protocols/api_support/util.py +403 -0
- opentrons/protocols/bundle.py +89 -0
- opentrons/protocols/duration/__init__.py +4 -0
- opentrons/protocols/duration/errors.py +5 -0
- opentrons/protocols/duration/estimator.py +628 -0
- opentrons/protocols/execution/__init__.py +0 -0
- opentrons/protocols/execution/dev_types.py +181 -0
- opentrons/protocols/execution/errors.py +40 -0
- opentrons/protocols/execution/execute.py +84 -0
- opentrons/protocols/execution/execute_json_v3.py +275 -0
- opentrons/protocols/execution/execute_json_v4.py +359 -0
- opentrons/protocols/execution/execute_json_v5.py +28 -0
- opentrons/protocols/execution/execute_python.py +169 -0
- opentrons/protocols/execution/json_dispatchers.py +87 -0
- opentrons/protocols/execution/types.py +7 -0
- opentrons/protocols/geometry/__init__.py +0 -0
- opentrons/protocols/geometry/planning.py +297 -0
- opentrons/protocols/labware.py +312 -0
- opentrons/protocols/models/__init__.py +0 -0
- opentrons/protocols/models/json_protocol.py +679 -0
- opentrons/protocols/parameters/__init__.py +0 -0
- opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
- opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
- opentrons/protocols/parameters/exceptions.py +34 -0
- opentrons/protocols/parameters/parameter_definition.py +272 -0
- opentrons/protocols/parameters/types.py +17 -0
- opentrons/protocols/parameters/validation.py +267 -0
- opentrons/protocols/parse.py +671 -0
- opentrons/protocols/types.py +159 -0
- opentrons/py.typed +0 -0
- opentrons/resources/scripts/lpc21isp +0 -0
- opentrons/resources/smoothie-edge-8414642.hex +23010 -0
- opentrons/simulate.py +1065 -0
- opentrons/system/__init__.py +6 -0
- opentrons/system/camera.py +51 -0
- opentrons/system/log_control.py +59 -0
- opentrons/system/nmcli.py +856 -0
- opentrons/system/resin.py +24 -0
- opentrons/system/smoothie_update.py +15 -0
- opentrons/system/wifi.py +204 -0
- opentrons/tools/__init__.py +0 -0
- opentrons/tools/args_handler.py +22 -0
- opentrons/tools/write_pipette_memory.py +157 -0
- opentrons/types.py +618 -0
- opentrons/util/__init__.py +1 -0
- opentrons/util/async_helpers.py +166 -0
- opentrons/util/broker.py +84 -0
- opentrons/util/change_notifier.py +47 -0
- opentrons/util/entrypoint_util.py +278 -0
- opentrons/util/get_union_elements.py +26 -0
- opentrons/util/helpers.py +6 -0
- opentrons/util/linal.py +178 -0
- opentrons/util/logging_config.py +265 -0
- opentrons/util/logging_queue_handler.py +61 -0
- opentrons/util/performance_helpers.py +157 -0
- opentrons-8.6.0.dist-info/METADATA +37 -0
- opentrons-8.6.0.dist-info/RECORD +601 -0
- opentrons-8.6.0.dist-info/WHEEL +4 -0
- opentrons-8.6.0.dist-info/entry_points.txt +3 -0
- opentrons-8.6.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,3272 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import logging
|
|
3
|
+
from contextlib import ExitStack
|
|
4
|
+
from typing import Any, List, Optional, Sequence, Union, cast, Tuple
|
|
5
|
+
from opentrons_shared_data.errors.exceptions import (
|
|
6
|
+
CommandPreconditionViolated,
|
|
7
|
+
CommandParameterLimitViolated,
|
|
8
|
+
UnexpectedTipRemovalError,
|
|
9
|
+
UnsupportedHardwareCommand,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from opentrons.legacy_broker import LegacyBroker
|
|
13
|
+
from opentrons.hardware_control.dev_types import PipetteDict
|
|
14
|
+
from opentrons import types
|
|
15
|
+
from opentrons.legacy_commands import (
|
|
16
|
+
commands as cmds,
|
|
17
|
+
protocol_commands as protocol_cmds,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from opentrons.legacy_commands import publisher
|
|
21
|
+
from opentrons.protocols.advanced_control.mix import mix_from_kwargs
|
|
22
|
+
from opentrons.protocols.advanced_control.transfers import transfer as v1_transfer
|
|
23
|
+
from opentrons.protocols.api_support.deck_type import NoTrashDefinedError
|
|
24
|
+
from opentrons.protocols.api_support.types import APIVersion
|
|
25
|
+
from opentrons.protocols.api_support import instrument
|
|
26
|
+
from opentrons.protocols.api_support.util import (
|
|
27
|
+
FlowRates,
|
|
28
|
+
PlungerSpeeds,
|
|
29
|
+
clamp_value,
|
|
30
|
+
requires_version,
|
|
31
|
+
APIVersionError,
|
|
32
|
+
UnsupportedAPIError,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from .core.common import InstrumentCore, ProtocolCore, WellCore
|
|
36
|
+
from .core.core_map import LoadedCoreMap
|
|
37
|
+
from .core.engine import ENGINE_CORE_API_VERSION
|
|
38
|
+
from .core.legacy.legacy_instrument_core import LegacyInstrumentCore
|
|
39
|
+
from .config import Clearances
|
|
40
|
+
from .disposal_locations import TrashBin, WasteChute
|
|
41
|
+
from ._nozzle_layout import NozzleLayout
|
|
42
|
+
from ._liquid import LiquidClass
|
|
43
|
+
from ._transfer_liquid_validation import (
|
|
44
|
+
verify_and_normalize_transfer_args,
|
|
45
|
+
resolve_keep_last_tip,
|
|
46
|
+
)
|
|
47
|
+
from . import labware, validation
|
|
48
|
+
from ..protocols.advanced_control.transfers.common import (
|
|
49
|
+
TransferTipPolicyV2,
|
|
50
|
+
TransferTipPolicyV2Type,
|
|
51
|
+
)
|
|
52
|
+
from ..protocol_engine.types import LiquidTrackingType
|
|
53
|
+
|
|
54
|
+
_DEFAULT_ASPIRATE_CLEARANCE = 1.0
|
|
55
|
+
_DEFAULT_DISPENSE_CLEARANCE = 1.0
|
|
56
|
+
|
|
57
|
+
_log = logging.getLogger(__name__)
|
|
58
|
+
|
|
59
|
+
_PREP_AFTER_ADDED_IN = APIVersion(2, 13)
|
|
60
|
+
"""The version after which the pick-up tip procedure should also prepare the plunger."""
|
|
61
|
+
_PRESSES_INCREMENT_REMOVED_IN = APIVersion(2, 14)
|
|
62
|
+
"""The version after which the pick-up tip procedure deprecates presses and increment arguments."""
|
|
63
|
+
_DROP_TIP_LOCATION_ALTERNATING_ADDED_IN = APIVersion(2, 15)
|
|
64
|
+
"""The version after which a drop-tip-into-trash procedure drops tips in different alternating locations within the trash well."""
|
|
65
|
+
_PARTIAL_NOZZLE_CONFIGURATION_ADDED_IN = APIVersion(2, 16)
|
|
66
|
+
"""The version after which a partial nozzle configuration became available for the 96 Channel Pipette."""
|
|
67
|
+
_PARTIAL_NOZZLE_CONFIGURATION_AUTOMATIC_TIP_TRACKING_IN = APIVersion(2, 18)
|
|
68
|
+
"""The version after which automatic tip tracking supported partially configured nozzle layouts."""
|
|
69
|
+
_DISPOSAL_LOCATION_OFFSET_ADDED_IN = APIVersion(2, 18)
|
|
70
|
+
"""The version after which offsets for deck configured trash containers and changes to alternating tip drop behavior were introduced."""
|
|
71
|
+
_PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN = APIVersion(2, 20)
|
|
72
|
+
"""The version after which partial nozzle configurations of single, row, and partial column layouts became available."""
|
|
73
|
+
_AIR_GAP_TRACKING_ADDED_IN = APIVersion(2, 22)
|
|
74
|
+
"""The version after which air gaps should be implemented with a separate call instead of an aspirate for better liquid volume tracking."""
|
|
75
|
+
_LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN = APIVersion(2, 25)
|
|
76
|
+
"""The version after which the user can supply liquid class transfers with non-assigned tip racks."""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
AdvancedLiquidHandling = v1_transfer.AdvancedLiquidHandling
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class _Unset:
|
|
83
|
+
"""A sentinel value when no value has been supplied for an argument.
|
|
84
|
+
User code should never use this explicitly."""
|
|
85
|
+
|
|
86
|
+
def __repr__(self) -> str:
|
|
87
|
+
# Without this, the generated docs render the argument as
|
|
88
|
+
# "<opentrons.protocol_api.instrument_context._Unset object at 0x1234>"
|
|
89
|
+
return self.__class__.__name__
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class InstrumentContext(publisher.CommandPublisher):
|
|
93
|
+
"""
|
|
94
|
+
A context for a specific pipette or instrument.
|
|
95
|
+
|
|
96
|
+
The InstrumentContext class provides the objects, attributes, and methods that allow
|
|
97
|
+
you to use pipettes in your protocols.
|
|
98
|
+
|
|
99
|
+
Methods generally fall into one of two categories.
|
|
100
|
+
|
|
101
|
+
- They can change the state of the InstrumentContext object, like how fast it
|
|
102
|
+
moves liquid or where it disposes of used tips.
|
|
103
|
+
|
|
104
|
+
- They can command the instrument to perform an action, like picking up tips,
|
|
105
|
+
moving to certain locations, and aspirating or dispensing liquid.
|
|
106
|
+
|
|
107
|
+
Objects in this class should not be instantiated directly. Instead, instances are
|
|
108
|
+
returned by :py:meth:`ProtocolContext.load_instrument`.
|
|
109
|
+
|
|
110
|
+
.. versionadded:: 2.0
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
core: InstrumentCore,
|
|
117
|
+
protocol_core: ProtocolCore,
|
|
118
|
+
broker: LegacyBroker,
|
|
119
|
+
api_version: APIVersion,
|
|
120
|
+
tip_racks: List[labware.Labware],
|
|
121
|
+
trash: Optional[Union[labware.Labware, TrashBin, WasteChute]],
|
|
122
|
+
requested_as: str,
|
|
123
|
+
core_map: LoadedCoreMap,
|
|
124
|
+
) -> None:
|
|
125
|
+
super().__init__(broker)
|
|
126
|
+
self._api_version = api_version
|
|
127
|
+
self._core = core
|
|
128
|
+
self._protocol_core = protocol_core
|
|
129
|
+
self._tip_racks = tip_racks
|
|
130
|
+
self._starting_tip: Union[labware.Well, None] = None
|
|
131
|
+
self._well_bottom_clearances = Clearances(
|
|
132
|
+
default_aspirate=_DEFAULT_ASPIRATE_CLEARANCE,
|
|
133
|
+
default_dispense=_DEFAULT_DISPENSE_CLEARANCE,
|
|
134
|
+
)
|
|
135
|
+
self._user_specified_trash: Union[
|
|
136
|
+
labware.Labware, TrashBin, WasteChute, None
|
|
137
|
+
] = trash
|
|
138
|
+
self.requested_as = requested_as
|
|
139
|
+
self._core_map = core_map
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
@requires_version(2, 0)
|
|
143
|
+
def api_version(self) -> APIVersion:
|
|
144
|
+
return self._api_version
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
@requires_version(2, 0)
|
|
148
|
+
def starting_tip(self) -> Union[labware.Well, None]:
|
|
149
|
+
"""
|
|
150
|
+
Which well of a tip rack the pipette should start at when automatically choosing tips to pick up.
|
|
151
|
+
|
|
152
|
+
See :py:meth:`.pick_up_tip()`.
|
|
153
|
+
|
|
154
|
+
.. note::
|
|
155
|
+
|
|
156
|
+
In robot software versions 6.3.0 and 6.3.1, protocols specifying API level
|
|
157
|
+
2.14 ignored ``starting_tip`` on the second and subsequent calls to
|
|
158
|
+
:py:meth:`.InstrumentContext.pick_up_tip` with no argument. This is fixed
|
|
159
|
+
for all API levels as of robot software version 7.0.0.
|
|
160
|
+
"""
|
|
161
|
+
return self._starting_tip
|
|
162
|
+
|
|
163
|
+
@starting_tip.setter
|
|
164
|
+
def starting_tip(self, location: Union[labware.Well, None]) -> None:
|
|
165
|
+
self._starting_tip = location
|
|
166
|
+
|
|
167
|
+
@requires_version(2, 0)
|
|
168
|
+
def reset_tipracks(self) -> None:
|
|
169
|
+
"""Reload all tips in each tip rack and reset the starting tip."""
|
|
170
|
+
for tiprack in self.tip_racks:
|
|
171
|
+
tiprack.reset()
|
|
172
|
+
self.starting_tip = None
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
@requires_version(2, 0)
|
|
176
|
+
def default_speed(self) -> float:
|
|
177
|
+
"""The speed at which the robot's gantry moves in mm/s.
|
|
178
|
+
|
|
179
|
+
The default speed for Flex varies between 300 and 350 mm/s. The OT-2 default is
|
|
180
|
+
400 mm/s. In addition to changing the default, the speed of individual motions
|
|
181
|
+
can be changed with the ``speed`` argument of the
|
|
182
|
+
:py:meth:`InstrumentContext.move_to` method. See :ref:`gantry_speed`.
|
|
183
|
+
"""
|
|
184
|
+
return self._core.get_default_speed()
|
|
185
|
+
|
|
186
|
+
@default_speed.setter
|
|
187
|
+
def default_speed(self, speed: float) -> None:
|
|
188
|
+
self._core.set_default_speed(speed)
|
|
189
|
+
|
|
190
|
+
@requires_version(2, 21)
|
|
191
|
+
def get_minimum_liquid_sense_height(self) -> float:
|
|
192
|
+
"""Get the minimum allowed height for liquid-level detection."""
|
|
193
|
+
return self._core.get_minimum_liquid_sense_height()
|
|
194
|
+
|
|
195
|
+
@requires_version(2, 0)
|
|
196
|
+
def aspirate( # noqa: C901
|
|
197
|
+
self,
|
|
198
|
+
volume: Optional[float] = None,
|
|
199
|
+
location: Optional[Union[types.Location, labware.Well]] = None,
|
|
200
|
+
rate: float = 1.0,
|
|
201
|
+
flow_rate: Optional[float] = None,
|
|
202
|
+
) -> InstrumentContext:
|
|
203
|
+
"""
|
|
204
|
+
Draw liquid into a pipette tip.
|
|
205
|
+
|
|
206
|
+
See :ref:`new-aspirate` for more details and examples.
|
|
207
|
+
|
|
208
|
+
:param volume: The volume to aspirate, measured in µL. If unspecified,
|
|
209
|
+
defaults to the maximum volume for the pipette and its currently
|
|
210
|
+
attached tip.
|
|
211
|
+
|
|
212
|
+
If ``aspirate`` is called with a volume of precisely 0, its behavior
|
|
213
|
+
depends on the API level of the protocol. On API levels below 2.16,
|
|
214
|
+
it will behave the same as a volume of ``None``/unspecified: aspirate
|
|
215
|
+
until the pipette is full. On API levels at or above 2.16, no liquid
|
|
216
|
+
will be aspirated.
|
|
217
|
+
:type volume: int or float
|
|
218
|
+
:param location: Tells the robot where to aspirate from. The location can be
|
|
219
|
+
a :py:class:`.Well` or a :py:class:`.Location`.
|
|
220
|
+
|
|
221
|
+
- If the location is a ``Well``, the robot will aspirate at
|
|
222
|
+
or above the bottom center of the well. The distance (in mm)
|
|
223
|
+
from the well bottom is specified by
|
|
224
|
+
:py:obj:`well_bottom_clearance.aspirate
|
|
225
|
+
<well_bottom_clearance>`.
|
|
226
|
+
|
|
227
|
+
- If the location is a ``Location`` (e.g., the result of
|
|
228
|
+
:py:meth:`.Well.top` or :py:meth:`.Well.bottom`), the robot
|
|
229
|
+
will aspirate from that specified position.
|
|
230
|
+
|
|
231
|
+
- If the ``location`` is unspecified, the robot will
|
|
232
|
+
aspirate from its current position.
|
|
233
|
+
:param rate: A multiplier for the default flow rate of the pipette. Calculated
|
|
234
|
+
as ``rate`` multiplied by :py:attr:`flow_rate.aspirate
|
|
235
|
+
<flow_rate>`. If not specified, defaults to 1.0. See
|
|
236
|
+
:ref:`new-plunger-flow-rates`.
|
|
237
|
+
:type rate: float
|
|
238
|
+
:param flow_rate: The absolute flow rate in µL/s. If ``flow_rate`` is specified,
|
|
239
|
+
``rate`` must not be set.
|
|
240
|
+
:type flow_rate: float
|
|
241
|
+
:returns: This instance.
|
|
242
|
+
|
|
243
|
+
.. note::
|
|
244
|
+
|
|
245
|
+
If ``aspirate`` is called with a single, unnamed argument, it will treat
|
|
246
|
+
that argument as ``volume``. If you want to call ``aspirate`` with only
|
|
247
|
+
``location``, specify it as a keyword argument:
|
|
248
|
+
``pipette.aspirate(location=plate['A1'])``
|
|
249
|
+
|
|
250
|
+
.. versionchanged:: 2.24
|
|
251
|
+
Added the ``flow_rate`` parameter.
|
|
252
|
+
"""
|
|
253
|
+
if flow_rate is not None:
|
|
254
|
+
if self.api_version < APIVersion(2, 24):
|
|
255
|
+
raise APIVersionError(
|
|
256
|
+
api_element="flow_rate",
|
|
257
|
+
until_version="2.24",
|
|
258
|
+
current_version=f"{self.api_version}",
|
|
259
|
+
)
|
|
260
|
+
if rate != 1.0:
|
|
261
|
+
raise ValueError("rate must not be set if flow_rate is specified")
|
|
262
|
+
rate = flow_rate / self._core.get_aspirate_flow_rate()
|
|
263
|
+
else:
|
|
264
|
+
flow_rate = self._core.get_aspirate_flow_rate(rate)
|
|
265
|
+
|
|
266
|
+
_log.debug(
|
|
267
|
+
"aspirate {} from {} at {} µL/s".format(
|
|
268
|
+
volume, location if location else "current position", flow_rate
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
move_to_location: types.Location
|
|
273
|
+
well: Optional[labware.Well]
|
|
274
|
+
last_location = self._get_last_location_by_api_version()
|
|
275
|
+
try:
|
|
276
|
+
target = validation.validate_location(
|
|
277
|
+
location=location, last_location=last_location
|
|
278
|
+
)
|
|
279
|
+
except validation.NoLocationError as e:
|
|
280
|
+
raise RuntimeError(
|
|
281
|
+
"If aspirate is called without an explicit location, another"
|
|
282
|
+
" method that moves to a location (such as move_to or "
|
|
283
|
+
"dispense) must previously have been called so the robot "
|
|
284
|
+
"knows where it is."
|
|
285
|
+
) from e
|
|
286
|
+
|
|
287
|
+
if isinstance(target, validation.DisposalTarget):
|
|
288
|
+
raise ValueError(
|
|
289
|
+
"Trash Bin and Waste Chute are not acceptable location parameters for Aspirate commands."
|
|
290
|
+
)
|
|
291
|
+
move_to_location, well, meniscus_tracking = self._handle_aspirate_target(
|
|
292
|
+
target=target
|
|
293
|
+
)
|
|
294
|
+
if self.api_version >= APIVersion(2, 11):
|
|
295
|
+
instrument.validate_takes_liquid(
|
|
296
|
+
location=move_to_location,
|
|
297
|
+
reject_module=self.api_version >= APIVersion(2, 13),
|
|
298
|
+
reject_adapter=self.api_version >= APIVersion(2, 15),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if self.api_version >= APIVersion(2, 16):
|
|
302
|
+
c_vol = self._core.get_available_volume() if volume is None else volume
|
|
303
|
+
else:
|
|
304
|
+
c_vol = self._core.get_available_volume() if not volume else volume
|
|
305
|
+
|
|
306
|
+
if (
|
|
307
|
+
self.api_version >= APIVersion(2, 20)
|
|
308
|
+
and well is not None
|
|
309
|
+
and self.liquid_presence_detection
|
|
310
|
+
and self._core.nozzle_configuration_valid_for_lld()
|
|
311
|
+
and self._core.get_current_volume() == 0
|
|
312
|
+
and self._core.get_has_clean_tip()
|
|
313
|
+
):
|
|
314
|
+
self._raise_if_pressure_not_supported_by_pipette()
|
|
315
|
+
self.require_liquid_presence(well=well)
|
|
316
|
+
|
|
317
|
+
with publisher.publish_context(
|
|
318
|
+
broker=self.broker,
|
|
319
|
+
command=cmds.aspirate(
|
|
320
|
+
instrument=self,
|
|
321
|
+
volume=c_vol,
|
|
322
|
+
location=move_to_location,
|
|
323
|
+
flow_rate=flow_rate,
|
|
324
|
+
rate=rate,
|
|
325
|
+
),
|
|
326
|
+
):
|
|
327
|
+
self._core.aspirate(
|
|
328
|
+
location=move_to_location,
|
|
329
|
+
well_core=well._core if well is not None else None,
|
|
330
|
+
volume=c_vol,
|
|
331
|
+
rate=rate,
|
|
332
|
+
flow_rate=flow_rate,
|
|
333
|
+
in_place=target.in_place,
|
|
334
|
+
meniscus_tracking=meniscus_tracking,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
return self
|
|
338
|
+
|
|
339
|
+
@requires_version(2, 0)
|
|
340
|
+
def dispense( # noqa: C901
|
|
341
|
+
self,
|
|
342
|
+
volume: Optional[float] = None,
|
|
343
|
+
location: Optional[
|
|
344
|
+
Union[types.Location, labware.Well, TrashBin, WasteChute]
|
|
345
|
+
] = None,
|
|
346
|
+
rate: float = 1.0,
|
|
347
|
+
push_out: Optional[float] = None,
|
|
348
|
+
flow_rate: Optional[float] = None,
|
|
349
|
+
) -> InstrumentContext:
|
|
350
|
+
"""
|
|
351
|
+
Dispense liquid from a pipette tip.
|
|
352
|
+
|
|
353
|
+
See :ref:`new-dispense` for more details and examples.
|
|
354
|
+
|
|
355
|
+
:param volume: The volume to dispense, measured in µL.
|
|
356
|
+
|
|
357
|
+
- If unspecified or ``None``, dispense the :py:attr:`current_volume`.
|
|
358
|
+
|
|
359
|
+
- If 0, the behavior of ``dispense()`` depends on the API level
|
|
360
|
+
of the protocol. In API version 2.16 and earlier, dispense all
|
|
361
|
+
liquid in the pipette (same as unspecified or ``None``). In API
|
|
362
|
+
version 2.17 and later, dispense no liquid.
|
|
363
|
+
|
|
364
|
+
- If greater than :py:obj:`.current_volume`, the behavior of
|
|
365
|
+
``dispense()`` depends on the API level of the protocol. In API
|
|
366
|
+
version 2.16 and earlier, dispense all liquid in the pipette.
|
|
367
|
+
In API version 2.17 and later, raise an error.
|
|
368
|
+
|
|
369
|
+
:type volume: int or float
|
|
370
|
+
|
|
371
|
+
:param location: Tells the robot where to dispense liquid held in the pipette.
|
|
372
|
+
The location can be a :py:class:`.Well`, :py:class:`.Location`,
|
|
373
|
+
:py:class:`.TrashBin`, or :py:class:`.WasteChute`.
|
|
374
|
+
|
|
375
|
+
- If a ``Well``, the pipette will dispense
|
|
376
|
+
at or above the bottom center of the well. The distance (in
|
|
377
|
+
mm) from the well bottom is specified by
|
|
378
|
+
:py:obj:`well_bottom_clearance.dispense
|
|
379
|
+
<well_bottom_clearance>`.
|
|
380
|
+
|
|
381
|
+
- If a ``Location`` (e.g., the result of
|
|
382
|
+
:py:meth:`.Well.top` or :py:meth:`.Well.bottom`), the pipette
|
|
383
|
+
will dispense at that specified position.
|
|
384
|
+
|
|
385
|
+
- If a trash container, the pipette will dispense at a location
|
|
386
|
+
relative to its center and the trash container's top center.
|
|
387
|
+
See :ref:`position-relative-trash` for details.
|
|
388
|
+
|
|
389
|
+
- If unspecified, the pipette will
|
|
390
|
+
dispense at its current position.
|
|
391
|
+
|
|
392
|
+
If only a ``location`` is passed (e.g.,
|
|
393
|
+
``pipette.dispense(location=plate['A1'])``), all of the
|
|
394
|
+
liquid aspirated into the pipette will be dispensed (the
|
|
395
|
+
amount is accessible through :py:attr:`current_volume`).
|
|
396
|
+
|
|
397
|
+
.. versionchanged:: 2.16
|
|
398
|
+
Accepts ``TrashBin`` and ``WasteChute`` values.
|
|
399
|
+
|
|
400
|
+
:param rate: How quickly a pipette dispenses liquid. The speed in µL/s is
|
|
401
|
+
calculated as ``rate`` multiplied by :py:attr:`flow_rate.dispense
|
|
402
|
+
<flow_rate>`. If not specified, defaults to 1.0. See
|
|
403
|
+
:ref:`new-plunger-flow-rates`.
|
|
404
|
+
:type rate: float
|
|
405
|
+
|
|
406
|
+
:param push_out: Continue past the plunger bottom to help ensure all liquid
|
|
407
|
+
leaves the tip. Measured in µL. The default value is ``None``.
|
|
408
|
+
|
|
409
|
+
When not specified or set to ``None``, the plunger moves by a non-zero default amount.
|
|
410
|
+
|
|
411
|
+
For a table of default values, see :ref:`push-out-dispense`.
|
|
412
|
+
:type push_out: float
|
|
413
|
+
|
|
414
|
+
:param flow_rate: The absolute flow rate in µL/s. If ``flow_rate`` is specified,
|
|
415
|
+
``rate`` must not be set.
|
|
416
|
+
:type flow_rate: float
|
|
417
|
+
|
|
418
|
+
:returns: This instance.
|
|
419
|
+
|
|
420
|
+
.. note::
|
|
421
|
+
|
|
422
|
+
If ``dispense`` is called with a single, unnamed argument, it will treat
|
|
423
|
+
that argument as ``volume``. If you want to call ``dispense`` with only
|
|
424
|
+
``location``, specify it as a keyword argument:
|
|
425
|
+
``pipette.dispense(location=plate['A1'])``.
|
|
426
|
+
|
|
427
|
+
.. versionchanged:: 2.15
|
|
428
|
+
Added the ``push_out`` parameter.
|
|
429
|
+
|
|
430
|
+
.. versionchanged:: 2.17
|
|
431
|
+
Behavior of the ``volume`` parameter.
|
|
432
|
+
|
|
433
|
+
.. versionchanged:: 2.24
|
|
434
|
+
Added the ``flow_rate`` parameter.
|
|
435
|
+
|
|
436
|
+
.. versionchanged:: 2.24
|
|
437
|
+
``location`` is no longer required if the pipette just moved to, dispensed, or blew out
|
|
438
|
+
into a trash bin or waste chute.
|
|
439
|
+
"""
|
|
440
|
+
if self.api_version < APIVersion(2, 15) and push_out:
|
|
441
|
+
raise APIVersionError(
|
|
442
|
+
api_element="Parameter push_out",
|
|
443
|
+
until_version="2.15",
|
|
444
|
+
current_version=f"{self.api_version}",
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
if flow_rate is not None:
|
|
448
|
+
if self.api_version < APIVersion(2, 24):
|
|
449
|
+
raise APIVersionError(
|
|
450
|
+
api_element="flow_rate",
|
|
451
|
+
until_version="2.24",
|
|
452
|
+
current_version=f"{self.api_version}",
|
|
453
|
+
)
|
|
454
|
+
if rate != 1.0:
|
|
455
|
+
raise ValueError("rate must not be set if flow_rate is specified")
|
|
456
|
+
rate = flow_rate / self._core.get_dispense_flow_rate()
|
|
457
|
+
else:
|
|
458
|
+
flow_rate = self._core.get_dispense_flow_rate(rate)
|
|
459
|
+
|
|
460
|
+
_log.debug(
|
|
461
|
+
"dispense {} from {} at {} µL/s".format(
|
|
462
|
+
volume, location if location else "current position", flow_rate
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
last_location = self._get_last_location_by_api_version()
|
|
467
|
+
try:
|
|
468
|
+
target = validation.validate_location(
|
|
469
|
+
location=location, last_location=last_location
|
|
470
|
+
)
|
|
471
|
+
except validation.NoLocationError as e:
|
|
472
|
+
raise RuntimeError(
|
|
473
|
+
"If dispense is called without an explicit location, another"
|
|
474
|
+
" method that moves to a location (such as move_to or "
|
|
475
|
+
"aspirate) must previously have been called so the robot "
|
|
476
|
+
"knows where it is."
|
|
477
|
+
) from e
|
|
478
|
+
|
|
479
|
+
if self.api_version >= APIVersion(2, 16):
|
|
480
|
+
c_vol = self._core.get_current_volume() if volume is None else volume
|
|
481
|
+
else:
|
|
482
|
+
c_vol = self._core.get_current_volume() if not volume else volume
|
|
483
|
+
|
|
484
|
+
if isinstance(target, validation.DisposalTarget):
|
|
485
|
+
with publisher.publish_context(
|
|
486
|
+
broker=self.broker,
|
|
487
|
+
command=cmds.dispense_in_disposal_location(
|
|
488
|
+
instrument=self,
|
|
489
|
+
volume=c_vol,
|
|
490
|
+
location=target.location,
|
|
491
|
+
rate=rate,
|
|
492
|
+
flow_rate=flow_rate,
|
|
493
|
+
),
|
|
494
|
+
):
|
|
495
|
+
self._core.dispense(
|
|
496
|
+
volume=c_vol,
|
|
497
|
+
rate=rate,
|
|
498
|
+
location=target.location,
|
|
499
|
+
well_core=None,
|
|
500
|
+
flow_rate=flow_rate,
|
|
501
|
+
in_place=target.in_place,
|
|
502
|
+
push_out=push_out,
|
|
503
|
+
meniscus_tracking=None,
|
|
504
|
+
)
|
|
505
|
+
return self
|
|
506
|
+
|
|
507
|
+
move_to_location, well, meniscus_tracking = self._handle_dispense_target(
|
|
508
|
+
target=target
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
if self.api_version >= APIVersion(2, 11):
|
|
512
|
+
instrument.validate_takes_liquid(
|
|
513
|
+
location=move_to_location,
|
|
514
|
+
reject_module=self.api_version >= APIVersion(2, 13),
|
|
515
|
+
reject_adapter=self.api_version >= APIVersion(2, 15),
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
with publisher.publish_context(
|
|
519
|
+
broker=self.broker,
|
|
520
|
+
command=cmds.dispense(
|
|
521
|
+
instrument=self,
|
|
522
|
+
volume=c_vol,
|
|
523
|
+
location=move_to_location,
|
|
524
|
+
rate=rate,
|
|
525
|
+
flow_rate=flow_rate,
|
|
526
|
+
),
|
|
527
|
+
):
|
|
528
|
+
self._core.dispense(
|
|
529
|
+
volume=c_vol,
|
|
530
|
+
rate=rate,
|
|
531
|
+
location=move_to_location,
|
|
532
|
+
well_core=well._core if well is not None else None,
|
|
533
|
+
flow_rate=flow_rate,
|
|
534
|
+
in_place=target.in_place,
|
|
535
|
+
push_out=push_out,
|
|
536
|
+
meniscus_tracking=meniscus_tracking,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
return self
|
|
540
|
+
|
|
541
|
+
@requires_version(2, 0)
|
|
542
|
+
def mix( # noqa: C901
|
|
543
|
+
self,
|
|
544
|
+
repetitions: int = 1,
|
|
545
|
+
volume: Optional[float] = None,
|
|
546
|
+
location: Optional[Union[types.Location, labware.Well]] = None,
|
|
547
|
+
rate: float = 1.0,
|
|
548
|
+
aspirate_flow_rate: Optional[float] = None,
|
|
549
|
+
dispense_flow_rate: Optional[float] = None,
|
|
550
|
+
aspirate_delay: Optional[float] = None,
|
|
551
|
+
dispense_delay: Optional[float] = None,
|
|
552
|
+
final_push_out: Optional[float] = None,
|
|
553
|
+
) -> InstrumentContext:
|
|
554
|
+
"""
|
|
555
|
+
Mix a volume of liquid by repeatedly aspirating and dispensing it in a single location.
|
|
556
|
+
|
|
557
|
+
See :ref:`mix` for examples.
|
|
558
|
+
|
|
559
|
+
:param repetitions: Number of times to mix (default is 1).
|
|
560
|
+
:param volume: The volume to mix, measured in µL. If unspecified, defaults
|
|
561
|
+
to the maximum volume for the pipette and its attached tip.
|
|
562
|
+
|
|
563
|
+
If ``mix`` is called with a volume of precisely 0, its behavior
|
|
564
|
+
depends on the API level of the protocol. On API levels below 2.16,
|
|
565
|
+
it will behave the same as a volume of ``None``/unspecified: mix
|
|
566
|
+
the full working volume of the pipette. On API levels at or above 2.16,
|
|
567
|
+
no liquid will be mixed.
|
|
568
|
+
:param location: The :py:class:`.Well` or :py:class:`~.types.Location` where the
|
|
569
|
+
pipette will mix. If unspecified, the pipette will mix at its
|
|
570
|
+
current position.
|
|
571
|
+
:param rate: How quickly the pipette aspirates and dispenses liquid while
|
|
572
|
+
mixing. The aspiration flow rate is calculated as ``rate``
|
|
573
|
+
multiplied by :py:attr:`flow_rate.aspirate <flow_rate>`. The
|
|
574
|
+
dispensing flow rate is calculated as ``rate`` multiplied by
|
|
575
|
+
:py:attr:`flow_rate.dispense <flow_rate>`. See
|
|
576
|
+
:ref:`new-plunger-flow-rates`.
|
|
577
|
+
:param aspirate_flow_rate: The absolute flow rate for each aspirate in the mix, in µL/s.
|
|
578
|
+
If this is specified, ``rate`` must not be set.
|
|
579
|
+
:param dispense_flow_rate: The absolute flow rate for each dispense in the mix, in µL/s.
|
|
580
|
+
If this is specified, ``rate`` must not be set.
|
|
581
|
+
:param aspirate_delay: How long to wait after each aspirate in the mix, in seconds.
|
|
582
|
+
:param dispense_delay: How long to wait after each dispense in the mix, in seconds.
|
|
583
|
+
:param final_push_out: How much volume to push out after the final mix repetition. The
|
|
584
|
+
pipette will not push out after earlier repetitions. If
|
|
585
|
+
not specified or ``None``, the pipette will push out the
|
|
586
|
+
default non-zero amount. See :ref:`push-out-dispense`.
|
|
587
|
+
:raises: ``UnexpectedTipRemovalError`` -- If no tip is attached to the pipette.
|
|
588
|
+
:returns: This instance.
|
|
589
|
+
|
|
590
|
+
.. note::
|
|
591
|
+
|
|
592
|
+
All the arguments of ``mix`` are optional. However, if you omit one of them,
|
|
593
|
+
all subsequent arguments must be passed as keyword arguments. For instance,
|
|
594
|
+
``pipette.mix(1, location=wellplate['A1'])`` is a valid call, but
|
|
595
|
+
``pipette.mix(1, wellplate['A1'])`` is not.
|
|
596
|
+
|
|
597
|
+
.. versionchanged:: 2.21
|
|
598
|
+
Does not repeatedly check for liquid presence.
|
|
599
|
+
.. versionchanged:: 2.24
|
|
600
|
+
Adds the ``aspirate_flow_rate``, ``dispense_flow_rate``, ``aspirate_delay``,
|
|
601
|
+
``dispense_delay``, and ``final_push_out`` parameters.
|
|
602
|
+
"""
|
|
603
|
+
_log.debug(
|
|
604
|
+
"mixing {}uL with {} repetitions in {} at rate={}".format(
|
|
605
|
+
volume, repetitions, location if location else "current position", rate
|
|
606
|
+
)
|
|
607
|
+
)
|
|
608
|
+
if not self._core.has_tip():
|
|
609
|
+
raise UnexpectedTipRemovalError("mix", self.name, self.mount)
|
|
610
|
+
|
|
611
|
+
if self.api_version >= APIVersion(2, 16):
|
|
612
|
+
c_vol = self._core.get_available_volume() if volume is None else volume
|
|
613
|
+
else:
|
|
614
|
+
c_vol = self._core.get_available_volume() if not volume else volume
|
|
615
|
+
|
|
616
|
+
if aspirate_flow_rate:
|
|
617
|
+
if self.api_version < APIVersion(2, 24):
|
|
618
|
+
raise APIVersionError(
|
|
619
|
+
api_element="aspirate_flow_rate",
|
|
620
|
+
until_version="2.24",
|
|
621
|
+
current_version=f"{self._api_version}",
|
|
622
|
+
)
|
|
623
|
+
if rate != 1.0:
|
|
624
|
+
raise ValueError(
|
|
625
|
+
"rate must not be set if aspirate_flow_rate is specified"
|
|
626
|
+
)
|
|
627
|
+
if dispense_flow_rate:
|
|
628
|
+
if self.api_version < APIVersion(2, 24):
|
|
629
|
+
raise APIVersionError(
|
|
630
|
+
api_element="dispense_flow_rate",
|
|
631
|
+
until_version="2.24",
|
|
632
|
+
current_version=f"{self._api_version}",
|
|
633
|
+
)
|
|
634
|
+
if rate != 1.0:
|
|
635
|
+
raise ValueError(
|
|
636
|
+
"rate must not be set if dispense_flow_rate is specified"
|
|
637
|
+
)
|
|
638
|
+
if aspirate_delay and self.api_version < APIVersion(2, 24):
|
|
639
|
+
raise APIVersionError(
|
|
640
|
+
api_element="aspirate_delay",
|
|
641
|
+
until_version="2.24",
|
|
642
|
+
current_version=f"{self._api_version}",
|
|
643
|
+
)
|
|
644
|
+
if dispense_delay and self.api_version < APIVersion(2, 24):
|
|
645
|
+
raise APIVersionError(
|
|
646
|
+
api_element="dispense_delay",
|
|
647
|
+
until_version="2.24",
|
|
648
|
+
current_version=f"{self._api_version}",
|
|
649
|
+
)
|
|
650
|
+
if final_push_out and self.api_version < APIVersion(2, 24):
|
|
651
|
+
raise APIVersionError(
|
|
652
|
+
api_element="final_push_out",
|
|
653
|
+
until_version="2.24",
|
|
654
|
+
current_version=f"{self._api_version}",
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
def delay_with_publish(seconds: float) -> None:
|
|
658
|
+
# We don't have access to ProtocolContext.delay() which would automatically
|
|
659
|
+
# publish a message to the broker, so we have to do it manually:
|
|
660
|
+
with publisher.publish_context(
|
|
661
|
+
broker=self.broker,
|
|
662
|
+
command=protocol_cmds.delay(seconds=seconds, minutes=0, msg=None),
|
|
663
|
+
):
|
|
664
|
+
self._protocol_core.delay(seconds=seconds, msg=None)
|
|
665
|
+
|
|
666
|
+
def aspirate_with_delay(
|
|
667
|
+
location: Optional[types.Location | labware.Well],
|
|
668
|
+
) -> None:
|
|
669
|
+
self.aspirate(volume, location, rate, flow_rate=aspirate_flow_rate)
|
|
670
|
+
if aspirate_delay:
|
|
671
|
+
delay_with_publish(aspirate_delay)
|
|
672
|
+
|
|
673
|
+
def dispense_with_delay(push_out: Optional[float]) -> None:
|
|
674
|
+
self.dispense(
|
|
675
|
+
volume, None, rate, flow_rate=dispense_flow_rate, push_out=push_out
|
|
676
|
+
)
|
|
677
|
+
if dispense_delay:
|
|
678
|
+
delay_with_publish(dispense_delay)
|
|
679
|
+
|
|
680
|
+
with publisher.publish_context(
|
|
681
|
+
broker=self.broker,
|
|
682
|
+
command=cmds.mix(
|
|
683
|
+
instrument=self,
|
|
684
|
+
repetitions=repetitions,
|
|
685
|
+
volume=c_vol,
|
|
686
|
+
location=location,
|
|
687
|
+
),
|
|
688
|
+
):
|
|
689
|
+
aspirate_with_delay(location=location)
|
|
690
|
+
with AutoProbeDisable(self):
|
|
691
|
+
while repetitions - 1 > 0:
|
|
692
|
+
# starting in 2.16, we disable push_out on all but the last
|
|
693
|
+
# dispense() to prevent the tip from jumping out of the liquid
|
|
694
|
+
# during the mix (PR #14004):
|
|
695
|
+
dispense_with_delay(
|
|
696
|
+
push_out=0 if self.api_version >= APIVersion(2, 16) else None
|
|
697
|
+
)
|
|
698
|
+
# aspirate location was set above, do subsequent aspirates in-place:
|
|
699
|
+
aspirate_with_delay(location=None)
|
|
700
|
+
repetitions -= 1
|
|
701
|
+
if final_push_out is not None:
|
|
702
|
+
dispense_with_delay(push_out=final_push_out)
|
|
703
|
+
else:
|
|
704
|
+
dispense_with_delay(push_out=None)
|
|
705
|
+
return self
|
|
706
|
+
|
|
707
|
+
@requires_version(2, 0)
|
|
708
|
+
def blow_out(
|
|
709
|
+
self,
|
|
710
|
+
location: Optional[
|
|
711
|
+
Union[types.Location, labware.Well, TrashBin, WasteChute]
|
|
712
|
+
] = None,
|
|
713
|
+
) -> InstrumentContext:
|
|
714
|
+
"""
|
|
715
|
+
Blow an extra amount of air through a pipette's tip to clear it.
|
|
716
|
+
|
|
717
|
+
If :py:meth:`dispense` is used to empty a pipette, usually a small amount of
|
|
718
|
+
liquid remains in the tip. During a blowout, the pipette moves the plunger
|
|
719
|
+
beyond its normal limits to help remove all liquid from the pipette tip. See
|
|
720
|
+
:ref:`blow-out`.
|
|
721
|
+
|
|
722
|
+
:param location: The blowout location. If no location is specified, the pipette
|
|
723
|
+
will blow out from its current position.
|
|
724
|
+
|
|
725
|
+
.. versionchanged:: 2.16
|
|
726
|
+
Accepts ``TrashBin`` and ``WasteChute`` values.
|
|
727
|
+
|
|
728
|
+
:type location: :py:class:`.Well` or :py:class:`.Location` or ``None``
|
|
729
|
+
|
|
730
|
+
:raises RuntimeError: If no location is specified and the location cache is
|
|
731
|
+
``None``. This should happen if ``blow_out()`` is called
|
|
732
|
+
without first calling a method that takes a location, like
|
|
733
|
+
:py:meth:`.aspirate` or :py:meth:`dispense`.
|
|
734
|
+
:returns: This instance.
|
|
735
|
+
|
|
736
|
+
.. versionchanged:: 2.24
|
|
737
|
+
``location`` is no longer required if the pipette just moved to, dispensed, or blew out
|
|
738
|
+
into a trash bin or waste chute.
|
|
739
|
+
"""
|
|
740
|
+
well: Optional[labware.Well] = None
|
|
741
|
+
move_to_location: types.Location
|
|
742
|
+
|
|
743
|
+
last_location = self._get_last_location_by_api_version()
|
|
744
|
+
try:
|
|
745
|
+
target = validation.validate_location(
|
|
746
|
+
location=location, last_location=last_location
|
|
747
|
+
)
|
|
748
|
+
except validation.NoLocationError as e:
|
|
749
|
+
raise RuntimeError(
|
|
750
|
+
"If blow out is called without an explicit location, another"
|
|
751
|
+
" method that moves to a location (such as move_to or "
|
|
752
|
+
"dispense) must previously have been called so the robot "
|
|
753
|
+
"knows where it is."
|
|
754
|
+
) from e
|
|
755
|
+
|
|
756
|
+
if isinstance(target, validation.WellTarget):
|
|
757
|
+
if target.well.parent.is_tiprack:
|
|
758
|
+
_log.warning(
|
|
759
|
+
"Blow_out being performed on a tiprack. "
|
|
760
|
+
"Please re-check your code"
|
|
761
|
+
)
|
|
762
|
+
if target.location:
|
|
763
|
+
# because the lower levels of blowout don't handle LiquidHandlingWellLocation and
|
|
764
|
+
# there is no "operation_volume" for blowout we need to convert the relative location
|
|
765
|
+
# given with a .meniscus to an absolute point. To maintain the meniscus behavior
|
|
766
|
+
# we can just add the offset to the current liquid height.
|
|
767
|
+
if target.location.meniscus_tracking:
|
|
768
|
+
move_to_location = target.well.bottom(
|
|
769
|
+
target.well.current_liquid_height() # type: ignore [arg-type]
|
|
770
|
+
+ target.location.point.z
|
|
771
|
+
)
|
|
772
|
+
else:
|
|
773
|
+
move_to_location = target.location
|
|
774
|
+
else:
|
|
775
|
+
move_to_location = target.well.top()
|
|
776
|
+
well = target.well
|
|
777
|
+
elif isinstance(target, validation.PointTarget):
|
|
778
|
+
move_to_location = target.location
|
|
779
|
+
elif isinstance(target, validation.DisposalTarget):
|
|
780
|
+
with publisher.publish_context(
|
|
781
|
+
broker=self.broker,
|
|
782
|
+
command=cmds.blow_out_in_disposal_location(
|
|
783
|
+
instrument=self, location=target.location
|
|
784
|
+
),
|
|
785
|
+
):
|
|
786
|
+
self._core.blow_out(
|
|
787
|
+
location=target.location,
|
|
788
|
+
well_core=None,
|
|
789
|
+
in_place=target.in_place,
|
|
790
|
+
)
|
|
791
|
+
return self
|
|
792
|
+
|
|
793
|
+
with publisher.publish_context(
|
|
794
|
+
broker=self.broker,
|
|
795
|
+
command=cmds.blow_out(instrument=self, location=move_to_location),
|
|
796
|
+
):
|
|
797
|
+
self._core.blow_out(
|
|
798
|
+
location=move_to_location,
|
|
799
|
+
well_core=well._core if well is not None else None,
|
|
800
|
+
in_place=target.in_place,
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
return self
|
|
804
|
+
|
|
805
|
+
def _determine_speed(self, speed: float) -> float:
|
|
806
|
+
if self.api_version < APIVersion(2, 4):
|
|
807
|
+
return clamp_value(speed, 80, 20, "touch_tip:")
|
|
808
|
+
else:
|
|
809
|
+
return clamp_value(speed, 80, 1, "touch_tip:")
|
|
810
|
+
|
|
811
|
+
@publisher.publish(command=cmds.touch_tip)
|
|
812
|
+
@requires_version(2, 0)
|
|
813
|
+
def touch_tip( # noqa: C901
|
|
814
|
+
self,
|
|
815
|
+
location: Optional[labware.Well] = None,
|
|
816
|
+
radius: float = 1.0,
|
|
817
|
+
v_offset: float = -1.0,
|
|
818
|
+
speed: float = 60.0,
|
|
819
|
+
mm_from_edge: Union[float, _Unset] = _Unset(),
|
|
820
|
+
) -> InstrumentContext:
|
|
821
|
+
"""
|
|
822
|
+
Touch the pipette tip to the sides of a well, with the intent of removing leftover droplets.
|
|
823
|
+
|
|
824
|
+
See :ref:`touch-tip` for more details and examples.
|
|
825
|
+
|
|
826
|
+
:param location: If no location is passed, the pipette will touch its tip at the
|
|
827
|
+
edges of the current well.
|
|
828
|
+
:type location: :py:class:`.Well` or ``None``
|
|
829
|
+
:param radius: How far to move, as a proportion of the target well's radius.
|
|
830
|
+
When ``radius=1.0``, the pipette tip will move all the way to the
|
|
831
|
+
edge of the target well. When ``radius=0.5``, it will move to 50%
|
|
832
|
+
of the well's radius. Default is 1.0 (100%)
|
|
833
|
+
:type radius: float
|
|
834
|
+
:param v_offset: How far above or below the well to touch the tip, measured in mm.
|
|
835
|
+
A positive offset moves the tip higher above the well.
|
|
836
|
+
A negative offset moves the tip lower into the well.
|
|
837
|
+
Default is -1.0 mm.
|
|
838
|
+
:type v_offset: float
|
|
839
|
+
:param speed: The speed for touch tip motion, in mm/s.
|
|
840
|
+
|
|
841
|
+
- Default: 60.0 mm/s
|
|
842
|
+
- Maximum: 80.0 mm/s
|
|
843
|
+
- Minimum: 1.0 mm/s
|
|
844
|
+
:type speed: float
|
|
845
|
+
:param mm_from_edge: How far to move inside the well, as a distance from the
|
|
846
|
+
well's edge.
|
|
847
|
+
When ``mm_from_edge=0``, the pipette will move to the target well's edge to touch the tip. When ``mm_from_edge=1``,
|
|
848
|
+
the pipette will move to 1 mm from the target well's edge to touch the tip.
|
|
849
|
+
Values lower than 0 will press the tip harder into the target well's
|
|
850
|
+
walls; higher values will touch the well more lightly, or
|
|
851
|
+
not at all.
|
|
852
|
+
``mm_from_edge`` and ``radius`` are mutually exclusive: to
|
|
853
|
+
use ``mm_from_edge``, ``radius`` must be unspecified (left
|
|
854
|
+
to its default value of 1.0).
|
|
855
|
+
:type mm_from_edge: float
|
|
856
|
+
:raises: ``UnexpectedTipRemovalError`` -- If no tip is attached to the pipette.
|
|
857
|
+
:raises RuntimeError: If no location is specified and the location cache is
|
|
858
|
+
``None``. This should happen if ``touch_tip`` is called
|
|
859
|
+
without first calling a method that takes a location, like
|
|
860
|
+
:py:meth:`.aspirate` or :py:meth:`dispense`.
|
|
861
|
+
:raises: ValueError: If both ``mm_from_edge`` and ``radius`` are specified.
|
|
862
|
+
:returns: This instance.
|
|
863
|
+
|
|
864
|
+
.. versionchanged:: 2.24
|
|
865
|
+
Added the ``mm_from_edge`` parameter.
|
|
866
|
+
"""
|
|
867
|
+
if not self._core.has_tip():
|
|
868
|
+
raise UnexpectedTipRemovalError("touch_tip", self.name, self.mount)
|
|
869
|
+
|
|
870
|
+
checked_speed = self._determine_speed(speed)
|
|
871
|
+
|
|
872
|
+
# If location is a valid well, move to the well first
|
|
873
|
+
if location is None:
|
|
874
|
+
last_location = self._protocol_core.get_last_location()
|
|
875
|
+
if last_location is None or isinstance(
|
|
876
|
+
last_location, (TrashBin, WasteChute)
|
|
877
|
+
):
|
|
878
|
+
raise RuntimeError(
|
|
879
|
+
f"Cached location of {last_location} is not valid for touch tip."
|
|
880
|
+
)
|
|
881
|
+
parent_labware, well = last_location.labware.get_parent_labware_and_well()
|
|
882
|
+
if not well or not parent_labware:
|
|
883
|
+
raise RuntimeError(
|
|
884
|
+
f"Last location {location} has no associated well or labware."
|
|
885
|
+
)
|
|
886
|
+
elif isinstance(location, labware.Well):
|
|
887
|
+
well = location
|
|
888
|
+
parent_labware = well.parent
|
|
889
|
+
else:
|
|
890
|
+
raise TypeError(f"location should be a Well, but it is {location}")
|
|
891
|
+
|
|
892
|
+
if not isinstance(mm_from_edge, _Unset):
|
|
893
|
+
if self.api_version < APIVersion(2, 24):
|
|
894
|
+
raise APIVersionError(
|
|
895
|
+
api_element="mm_from_edge",
|
|
896
|
+
until_version="2.24",
|
|
897
|
+
current_version=f"{self.api_version}",
|
|
898
|
+
)
|
|
899
|
+
if radius != 1.0:
|
|
900
|
+
raise ValueError(
|
|
901
|
+
"radius must be set to 1.0 if mm_from_edge is specified"
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
if "touchTipDisabled" in parent_labware.quirks:
|
|
905
|
+
_log.info(f"Ignoring touch tip on labware {well}")
|
|
906
|
+
return self
|
|
907
|
+
if parent_labware.is_tiprack:
|
|
908
|
+
_log.warning(
|
|
909
|
+
"Touch_tip being performed on a tiprack. Please re-check your code"
|
|
910
|
+
)
|
|
911
|
+
|
|
912
|
+
if self.api_version < APIVersion(2, 4):
|
|
913
|
+
move_to_location = well.top()
|
|
914
|
+
else:
|
|
915
|
+
move_to_location = well.top(z=v_offset)
|
|
916
|
+
|
|
917
|
+
self._core.touch_tip(
|
|
918
|
+
location=move_to_location,
|
|
919
|
+
well_core=well._core,
|
|
920
|
+
radius=radius,
|
|
921
|
+
z_offset=v_offset,
|
|
922
|
+
speed=checked_speed,
|
|
923
|
+
mm_from_edge=mm_from_edge if not isinstance(mm_from_edge, _Unset) else None,
|
|
924
|
+
)
|
|
925
|
+
return self
|
|
926
|
+
|
|
927
|
+
@publisher.publish(command=cmds.air_gap)
|
|
928
|
+
@requires_version(2, 0)
|
|
929
|
+
def air_gap( # noqa: C901
|
|
930
|
+
self,
|
|
931
|
+
volume: Optional[float] = None,
|
|
932
|
+
height: Optional[float] = None,
|
|
933
|
+
in_place: Optional[bool] = None,
|
|
934
|
+
rate: Optional[float] = None,
|
|
935
|
+
flow_rate: Optional[float] = None,
|
|
936
|
+
) -> InstrumentContext:
|
|
937
|
+
"""
|
|
938
|
+
Draw air into the pipette's tip at the current well.
|
|
939
|
+
|
|
940
|
+
See :ref:`air-gap`.
|
|
941
|
+
|
|
942
|
+
:param volume: The amount of air, measured in µL. Calling ``air_gap()`` with no
|
|
943
|
+
arguments uses the entire remaining volume in the pipette.
|
|
944
|
+
:type volume: float
|
|
945
|
+
|
|
946
|
+
:param height: The height, in mm, to move above the current well before creating
|
|
947
|
+
the air gap. The default is 5 mm above the current well.
|
|
948
|
+
:type height: float
|
|
949
|
+
|
|
950
|
+
:param in_place: Air gap at the pipette's current position, without moving to
|
|
951
|
+
some height above the well. If ``in_place`` is specified,
|
|
952
|
+
``height`` must be unset.
|
|
953
|
+
:type in_place: bool
|
|
954
|
+
|
|
955
|
+
:param rate: A multiplier for the default flow rate of the pipette. Calculated
|
|
956
|
+
as ``rate`` multiplied by :py:attr:`flow_rate.aspirate
|
|
957
|
+
<flow_rate>`. If neither rate nor flow_rate is specified, the pipette
|
|
958
|
+
will aspirate at a rate of 1.0 * InstrumentContext.flow_rate.aspirate. See
|
|
959
|
+
:ref:`new-plunger-flow-rates`.
|
|
960
|
+
:type rate: float
|
|
961
|
+
|
|
962
|
+
:param flow_rate: The rate, in µL/s, at which the pipette will draw in air.
|
|
963
|
+
:type flow_rate: float
|
|
964
|
+
|
|
965
|
+
:raises: ``UnexpectedTipRemovalError`` -- If no tip is attached to the pipette.
|
|
966
|
+
|
|
967
|
+
:raises RuntimeError: If location cache is ``None`` and the air gap is not
|
|
968
|
+
``in_place``. This would happen if ``air_gap()`` is called
|
|
969
|
+
without first calling a method that takes a location (e.g.,
|
|
970
|
+
:py:meth:`.aspirate`, :py:meth:`dispense`)
|
|
971
|
+
|
|
972
|
+
:returns: This instance.
|
|
973
|
+
|
|
974
|
+
Both ``volume`` and ``height`` are optional, but if you want to specify only
|
|
975
|
+
``height`` you must do it as a keyword argument:
|
|
976
|
+
``pipette.air_gap(height=2)``. If you call ``air_gap`` with a single,
|
|
977
|
+
unnamed argument, it will always be interpreted as a volume.
|
|
978
|
+
|
|
979
|
+
.. note::
|
|
980
|
+
|
|
981
|
+
In API version 2.21 and earlier, this function was implemented as an aspirate, and
|
|
982
|
+
dispensing into a well would add the air gap volume to the liquid tracked in
|
|
983
|
+
the well. In API version 2.22 and later, air gap volume is not tracked as liquid
|
|
984
|
+
when dispensing into a well.
|
|
985
|
+
|
|
986
|
+
.. versionchanged:: 2.22
|
|
987
|
+
No longer implemented as an aspirate.
|
|
988
|
+
.. versionchanged:: 2.24
|
|
989
|
+
Added the ``in_place`` option.
|
|
990
|
+
.. versionchanged:: 2.24
|
|
991
|
+
Adds the ``rate`` and ``flow_rate`` parameter. You can only define one or the other. If
|
|
992
|
+
both are unspecified then ``rate`` is by default set to 1.0.
|
|
993
|
+
Can air gap over a trash bin or waste chute.
|
|
994
|
+
"""
|
|
995
|
+
if not self._core.has_tip():
|
|
996
|
+
raise UnexpectedTipRemovalError("air_gap", self.name, self.mount)
|
|
997
|
+
|
|
998
|
+
if rate is not None and self.api_version < APIVersion(2, 24):
|
|
999
|
+
raise APIVersionError(
|
|
1000
|
+
api_element="rate",
|
|
1001
|
+
until_version="2.24",
|
|
1002
|
+
current_version=f"{self._api_version}",
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
if flow_rate is not None and self.api_version < APIVersion(2, 24):
|
|
1006
|
+
raise APIVersionError(
|
|
1007
|
+
api_element="flow_rate",
|
|
1008
|
+
until_version="2.24",
|
|
1009
|
+
current_version=f"{self._api_version}",
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
if flow_rate is not None and rate is not None:
|
|
1013
|
+
raise ValueError("Cannot define both flow_rate and rate.")
|
|
1014
|
+
|
|
1015
|
+
if in_place:
|
|
1016
|
+
if self.api_version < APIVersion(2, 24):
|
|
1017
|
+
raise APIVersionError(
|
|
1018
|
+
api_element="in_place",
|
|
1019
|
+
until_version="2.24",
|
|
1020
|
+
current_version=f"{self._api_version}",
|
|
1021
|
+
)
|
|
1022
|
+
if height is not None:
|
|
1023
|
+
raise ValueError("height must be unset if air gapping in_place")
|
|
1024
|
+
else:
|
|
1025
|
+
if height is None:
|
|
1026
|
+
height = 5
|
|
1027
|
+
last_location = self._protocol_core.get_last_location()
|
|
1028
|
+
if self.api_version < APIVersion(2, 24) and isinstance(
|
|
1029
|
+
last_location, (TrashBin, WasteChute)
|
|
1030
|
+
):
|
|
1031
|
+
last_location = None
|
|
1032
|
+
if last_location is None or (
|
|
1033
|
+
isinstance(last_location, types.Location)
|
|
1034
|
+
and not last_location.labware.is_well
|
|
1035
|
+
):
|
|
1036
|
+
raise RuntimeError(
|
|
1037
|
+
f"Cached location of {last_location} is not valid for air gap."
|
|
1038
|
+
)
|
|
1039
|
+
target: Union[types.Location, TrashBin, WasteChute]
|
|
1040
|
+
if isinstance(last_location, types.Location):
|
|
1041
|
+
target = last_location.labware.as_well().top(height)
|
|
1042
|
+
else:
|
|
1043
|
+
target = last_location.top(height)
|
|
1044
|
+
self.move_to(target, publish=False)
|
|
1045
|
+
|
|
1046
|
+
if self.api_version >= _AIR_GAP_TRACKING_ADDED_IN:
|
|
1047
|
+
self._core.prepare_to_aspirate()
|
|
1048
|
+
c_vol = self._core.get_available_volume() if volume is None else volume
|
|
1049
|
+
if flow_rate is not None:
|
|
1050
|
+
calculated_rate = flow_rate
|
|
1051
|
+
elif rate is not None:
|
|
1052
|
+
calculated_rate = rate * self._core.get_aspirate_flow_rate()
|
|
1053
|
+
else:
|
|
1054
|
+
calculated_rate = self._core.get_aspirate_flow_rate()
|
|
1055
|
+
|
|
1056
|
+
self._core.air_gap_in_place(c_vol, calculated_rate)
|
|
1057
|
+
else:
|
|
1058
|
+
self.aspirate(volume)
|
|
1059
|
+
return self
|
|
1060
|
+
|
|
1061
|
+
@publisher.publish(command=cmds.return_tip)
|
|
1062
|
+
@requires_version(2, 0)
|
|
1063
|
+
def return_tip(self, home_after: Optional[bool] = None) -> InstrumentContext:
|
|
1064
|
+
"""
|
|
1065
|
+
Drop the currently attached tip in its original location in the tip rack.
|
|
1066
|
+
|
|
1067
|
+
Returning a tip does not reset tip tracking, so :py:obj:`.Well.has_tip` will
|
|
1068
|
+
remain ``False`` for the destination.
|
|
1069
|
+
|
|
1070
|
+
:returns: This instance.
|
|
1071
|
+
|
|
1072
|
+
:param home_after: See the ``home_after`` parameter of :py:meth:`drop_tip`.
|
|
1073
|
+
"""
|
|
1074
|
+
if not self._core.has_tip():
|
|
1075
|
+
_log.warning("Pipette has no tip to return")
|
|
1076
|
+
|
|
1077
|
+
loc = self._get_current_tip_source_well()
|
|
1078
|
+
|
|
1079
|
+
# TODO rewrite this error message
|
|
1080
|
+
if not isinstance(loc, labware.Well):
|
|
1081
|
+
raise TypeError(f"Last tip location should be a Well but it is: {loc}")
|
|
1082
|
+
|
|
1083
|
+
self.drop_tip(loc, home_after=home_after)
|
|
1084
|
+
|
|
1085
|
+
return self
|
|
1086
|
+
|
|
1087
|
+
@requires_version(2, 0)
|
|
1088
|
+
def pick_up_tip( # noqa: C901
|
|
1089
|
+
self,
|
|
1090
|
+
location: Union[types.Location, labware.Well, labware.Labware, None] = None,
|
|
1091
|
+
presses: Optional[int] = None,
|
|
1092
|
+
increment: Optional[float] = None,
|
|
1093
|
+
prep_after: Optional[bool] = None,
|
|
1094
|
+
) -> InstrumentContext:
|
|
1095
|
+
"""
|
|
1096
|
+
Pick up a tip for the pipette to run liquid-handling commands.
|
|
1097
|
+
|
|
1098
|
+
See :ref:`basic-tip-pickup`.
|
|
1099
|
+
|
|
1100
|
+
If no location is passed, the pipette will pick up the next available tip in its
|
|
1101
|
+
:py:attr:`~.InstrumentContext.tip_racks` list. Within each tip rack, tips will
|
|
1102
|
+
be picked up in the order specified by the labware definition and
|
|
1103
|
+
:py:meth:`.Labware.wells`. To adjust where the sequence starts, use
|
|
1104
|
+
:py:obj:`.starting_tip`.
|
|
1105
|
+
|
|
1106
|
+
The exact position for tip pickup accounts for the length of the tip and how
|
|
1107
|
+
much the tip overlaps with the pipette nozzle. These measurements are fixed
|
|
1108
|
+
values on Flex, and are based on the results of tip length calibration on OT-2.
|
|
1109
|
+
|
|
1110
|
+
.. note::
|
|
1111
|
+
API version 2.19 updates the tip overlap values for Flex. When updating a
|
|
1112
|
+
protocol from 2.18 (or lower) to 2.19 (or higher), pipette performance
|
|
1113
|
+
should improve without additional changes to your protocol. Nevertheless, it
|
|
1114
|
+
is good practice after updating to do the following:
|
|
1115
|
+
|
|
1116
|
+
- Run Labware Position Check.
|
|
1117
|
+
- Perform a dry run of your protocol.
|
|
1118
|
+
- If tip position is slightly higher than expected, adjust the ``location``
|
|
1119
|
+
parameter of pipetting actions to achieve the desired result.
|
|
1120
|
+
|
|
1121
|
+
:param location: The location from which to pick up a tip. The ``location``
|
|
1122
|
+
argument can be specified in several ways:
|
|
1123
|
+
|
|
1124
|
+
* As a :py:class:`.Well`. For example,
|
|
1125
|
+
``pipette.pick_up_tip(tiprack.wells()[0])`` will always pick
|
|
1126
|
+
up the first tip in ``tiprack``, even if the rack is not a
|
|
1127
|
+
member of :py:obj:`.InstrumentContext.tip_racks`.
|
|
1128
|
+
|
|
1129
|
+
* As a labware. ``pipette.pick_up_tip(tiprack)`` will pick up
|
|
1130
|
+
the next available tip in ``tiprack``, even if the rack is
|
|
1131
|
+
not a member of :py:obj:`.InstrumentContext.tip_racks`.
|
|
1132
|
+
|
|
1133
|
+
* As a :py:class:`~.types.Location`. Use this to make fine
|
|
1134
|
+
adjustments to the pickup location. For example, to tell
|
|
1135
|
+
the robot to start its pick up tip routine 1 mm closer to
|
|
1136
|
+
the top of the well in the tip rack, call
|
|
1137
|
+
``pipette.pick_up_tip(tiprack["A1"].top(z=-1))``.
|
|
1138
|
+
:type location: :py:class:`.Well` or :py:class:`.Labware` or :py:class:`.types.Location`
|
|
1139
|
+
:param presses: The number of times to lower and then raise the pipette when
|
|
1140
|
+
picking up a tip, to ensure a good seal. Zero (``0``) will
|
|
1141
|
+
result in the pipette hovering over the tip but not picking it
|
|
1142
|
+
up (generally not desirable, but could be used for a dry run).
|
|
1143
|
+
|
|
1144
|
+
.. deprecated:: 2.14
|
|
1145
|
+
Use the Opentrons App to change pipette pick-up settings.
|
|
1146
|
+
:type presses: int
|
|
1147
|
+
:param increment: The additional distance to travel on each successive press.
|
|
1148
|
+
For example, if ``presses=3`` and ``increment=1.0``, then the
|
|
1149
|
+
first press will travel down into the tip by 3.5 mm, the
|
|
1150
|
+
second by 4.5 mm, and the third by 5.5 mm).
|
|
1151
|
+
|
|
1152
|
+
.. deprecated:: 2.14
|
|
1153
|
+
Use the Opentrons App to change pipette pick-up settings.
|
|
1154
|
+
:type increment: float
|
|
1155
|
+
:param prep_after: Whether the pipette plunger should prepare itself to aspirate
|
|
1156
|
+
immediately after picking up a tip.
|
|
1157
|
+
|
|
1158
|
+
If ``True``, the pipette will move its plunger position to
|
|
1159
|
+
bottom in preparation for any following calls to
|
|
1160
|
+
:py:meth:`.aspirate`.
|
|
1161
|
+
|
|
1162
|
+
If ``False``, the pipette will prepare its plunger later,
|
|
1163
|
+
during the next call to :py:meth:`.aspirate`. This is
|
|
1164
|
+
accomplished by moving the tip to the top of the well, and
|
|
1165
|
+
positioning the plunger outside any potential liquids.
|
|
1166
|
+
|
|
1167
|
+
.. warning::
|
|
1168
|
+
This is provided for compatibility with older Python
|
|
1169
|
+
Protocol API behavior. You should normally leave this
|
|
1170
|
+
unset.
|
|
1171
|
+
|
|
1172
|
+
Setting ``prep_after=False`` may create an unintended
|
|
1173
|
+
pipette movement, when the pipette automatically moves
|
|
1174
|
+
the tip to the top of the well to prepare the plunger.
|
|
1175
|
+
:type prep_after: bool
|
|
1176
|
+
|
|
1177
|
+
.. versionchanged:: 2.13
|
|
1178
|
+
Adds the ``prep_after`` argument. In version 2.12 and earlier, the plunger
|
|
1179
|
+
can't prepare itself for aspiration during :py:meth:`.pick_up_tip`, and will
|
|
1180
|
+
instead always prepare during :py:meth:`.aspirate`. Version 2.12 and earlier
|
|
1181
|
+
will raise an ``APIVersionError`` if a value is set for ``prep_after``.
|
|
1182
|
+
|
|
1183
|
+
.. versionchanged:: 2.19
|
|
1184
|
+
Uses new values for how much a tip overlaps with the pipette nozzle.
|
|
1185
|
+
|
|
1186
|
+
:returns: This instance.
|
|
1187
|
+
"""
|
|
1188
|
+
|
|
1189
|
+
if presses is not None and self._api_version >= _PRESSES_INCREMENT_REMOVED_IN:
|
|
1190
|
+
raise UnsupportedAPIError(
|
|
1191
|
+
api_element="presses",
|
|
1192
|
+
since_version=f"{_PRESSES_INCREMENT_REMOVED_IN}",
|
|
1193
|
+
current_version=f"{self._api_version}",
|
|
1194
|
+
)
|
|
1195
|
+
|
|
1196
|
+
if increment is not None and self._api_version >= _PRESSES_INCREMENT_REMOVED_IN:
|
|
1197
|
+
raise UnsupportedAPIError(
|
|
1198
|
+
api_element="increment",
|
|
1199
|
+
since_version=f"{_PRESSES_INCREMENT_REMOVED_IN}",
|
|
1200
|
+
current_version=f"{self._api_version}",
|
|
1201
|
+
)
|
|
1202
|
+
|
|
1203
|
+
if prep_after is not None and self._api_version < _PREP_AFTER_ADDED_IN:
|
|
1204
|
+
raise APIVersionError(
|
|
1205
|
+
api_element="prep_after",
|
|
1206
|
+
until_version=f"{_PREP_AFTER_ADDED_IN}",
|
|
1207
|
+
current_version=f"{self._api_version}",
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
well: labware.Well
|
|
1211
|
+
tip_rack: labware.Labware
|
|
1212
|
+
move_to_location: Optional[types.Location] = None
|
|
1213
|
+
active_channels = (
|
|
1214
|
+
self.active_channels
|
|
1215
|
+
if self._api_version >= _PARTIAL_NOZZLE_CONFIGURATION_ADDED_IN
|
|
1216
|
+
else self.channels
|
|
1217
|
+
)
|
|
1218
|
+
nozzle_map = (
|
|
1219
|
+
self._core.get_nozzle_map()
|
|
1220
|
+
if self._api_version
|
|
1221
|
+
>= _PARTIAL_NOZZLE_CONFIGURATION_AUTOMATIC_TIP_TRACKING_IN
|
|
1222
|
+
else None
|
|
1223
|
+
)
|
|
1224
|
+
|
|
1225
|
+
if location is None:
|
|
1226
|
+
if (
|
|
1227
|
+
nozzle_map is not None
|
|
1228
|
+
and nozzle_map.configuration != types.NozzleConfigurationType.FULL
|
|
1229
|
+
and self.starting_tip is not None
|
|
1230
|
+
):
|
|
1231
|
+
# Disallowing this avoids concerning the system with the direction
|
|
1232
|
+
# in which self.starting_tip consumes tips. It would currently vary
|
|
1233
|
+
# depending on the configuration layout of a pipette at a given
|
|
1234
|
+
# time, which means that some combination of starting tip and partial
|
|
1235
|
+
# configuration are incompatible under the current understanding of
|
|
1236
|
+
# starting tip behavior. Replacing starting_tip with an un-deprecated
|
|
1237
|
+
# Labware.has_tip may solve this.
|
|
1238
|
+
raise CommandPreconditionViolated(
|
|
1239
|
+
"Automatic tip tracking is not available when using a partial pipette"
|
|
1240
|
+
" nozzle configuration and InstrumentContext.starting_tip."
|
|
1241
|
+
" Switch to a full configuration or set starting_tip to None."
|
|
1242
|
+
)
|
|
1243
|
+
if not self._core.is_tip_tracking_available():
|
|
1244
|
+
raise CommandPreconditionViolated(
|
|
1245
|
+
"Automatic tip tracking is not available for the current pipette"
|
|
1246
|
+
" nozzle configuration. We suggest switching to a configuration"
|
|
1247
|
+
" that supports automatic tip tracking or specifying the exact tip"
|
|
1248
|
+
" to pick up."
|
|
1249
|
+
)
|
|
1250
|
+
tip_rack, well = labware.next_available_tip(
|
|
1251
|
+
starting_tip=self.starting_tip,
|
|
1252
|
+
tip_racks=self.tip_racks,
|
|
1253
|
+
channels=active_channels,
|
|
1254
|
+
nozzle_map=nozzle_map,
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
elif isinstance(location, labware.Well):
|
|
1258
|
+
well = location
|
|
1259
|
+
tip_rack = well.parent
|
|
1260
|
+
|
|
1261
|
+
elif isinstance(location, labware.Labware):
|
|
1262
|
+
tip_rack, well = labware.next_available_tip(
|
|
1263
|
+
starting_tip=None,
|
|
1264
|
+
tip_racks=[location],
|
|
1265
|
+
channels=active_channels,
|
|
1266
|
+
nozzle_map=nozzle_map,
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1269
|
+
elif isinstance(location, types.Location):
|
|
1270
|
+
maybe_tip_rack, maybe_well = location.labware.get_parent_labware_and_well()
|
|
1271
|
+
|
|
1272
|
+
if maybe_well is not None:
|
|
1273
|
+
well = maybe_well
|
|
1274
|
+
tip_rack = well.parent
|
|
1275
|
+
move_to_location = location
|
|
1276
|
+
|
|
1277
|
+
elif maybe_tip_rack is not None:
|
|
1278
|
+
tip_rack, well = labware.next_available_tip(
|
|
1279
|
+
starting_tip=None,
|
|
1280
|
+
tip_racks=[maybe_tip_rack],
|
|
1281
|
+
channels=active_channels,
|
|
1282
|
+
nozzle_map=nozzle_map,
|
|
1283
|
+
)
|
|
1284
|
+
else:
|
|
1285
|
+
raise TypeError(
|
|
1286
|
+
"If specified as a `types.Location`,"
|
|
1287
|
+
" `location` should refer to a ``Labware` or `Well` location."
|
|
1288
|
+
f" However, it refers to {location.labware}"
|
|
1289
|
+
)
|
|
1290
|
+
|
|
1291
|
+
else:
|
|
1292
|
+
raise TypeError(
|
|
1293
|
+
"If specified, location should be an instance of"
|
|
1294
|
+
" `types.Location` (e.g. the return value from `Well.top()`),"
|
|
1295
|
+
" `Labware` or `Well` (e.g. `tiprack.wells()[0]`)."
|
|
1296
|
+
f" However, it is {location}"
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
instrument.validate_tiprack(self.name, tip_rack, _log)
|
|
1300
|
+
|
|
1301
|
+
move_to_location = move_to_location or well.top()
|
|
1302
|
+
prep_after = (
|
|
1303
|
+
prep_after
|
|
1304
|
+
if prep_after is not None
|
|
1305
|
+
else self.api_version >= _PREP_AFTER_ADDED_IN
|
|
1306
|
+
)
|
|
1307
|
+
|
|
1308
|
+
with publisher.publish_context(
|
|
1309
|
+
broker=self.broker,
|
|
1310
|
+
command=cmds.pick_up_tip(instrument=self, location=well),
|
|
1311
|
+
):
|
|
1312
|
+
self._core.pick_up_tip(
|
|
1313
|
+
location=move_to_location,
|
|
1314
|
+
well_core=well._core,
|
|
1315
|
+
presses=presses,
|
|
1316
|
+
increment=increment,
|
|
1317
|
+
prep_after=prep_after,
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
return self
|
|
1321
|
+
|
|
1322
|
+
@requires_version(2, 0)
|
|
1323
|
+
def drop_tip(
|
|
1324
|
+
self,
|
|
1325
|
+
location: Optional[
|
|
1326
|
+
Union[
|
|
1327
|
+
types.Location,
|
|
1328
|
+
labware.Well,
|
|
1329
|
+
TrashBin,
|
|
1330
|
+
WasteChute,
|
|
1331
|
+
]
|
|
1332
|
+
] = None,
|
|
1333
|
+
home_after: Optional[bool] = None,
|
|
1334
|
+
) -> InstrumentContext:
|
|
1335
|
+
"""
|
|
1336
|
+
Drop the current tip.
|
|
1337
|
+
|
|
1338
|
+
See :ref:`pipette-drop-tip` for examples.
|
|
1339
|
+
|
|
1340
|
+
If no location is passed (e.g. ``pipette.drop_tip()``), the pipette will drop
|
|
1341
|
+
the attached tip into its :py:attr:`trash_container`.
|
|
1342
|
+
|
|
1343
|
+
The location in which to drop the tip can be manually specified with the
|
|
1344
|
+
``location`` argument. The ``location`` argument can be specified in several
|
|
1345
|
+
ways:
|
|
1346
|
+
|
|
1347
|
+
- As a :py:class:`.Well`. This uses a default location relative to the well.
|
|
1348
|
+
This style of call can be used to make the robot drop a tip into labware
|
|
1349
|
+
like a well plate or a reservoir. For example,
|
|
1350
|
+
``pipette.drop_tip(location=reservoir["A1"])``.
|
|
1351
|
+
- As a :py:class:`~.types.Location`. For example, to drop a tip from an
|
|
1352
|
+
unusually large height above the tip rack, you could call
|
|
1353
|
+
``pipette.drop_tip(tip_rack["A1"].top(z=10))``.
|
|
1354
|
+
- As a :py:class:`.TrashBin`. This uses a default location relative to the
|
|
1355
|
+
``TrashBin`` object. For example,
|
|
1356
|
+
``pipette.drop_tip(location=trash_bin)``.
|
|
1357
|
+
- As a :py:class:`.WasteChute`. This uses a default location relative to
|
|
1358
|
+
the ``WasteChute`` object. For example,
|
|
1359
|
+
``pipette.drop_tip(location=waste_chute)``.
|
|
1360
|
+
|
|
1361
|
+
In API versions 2.15 to 2.17, if ``location`` is a ``TrashBin`` or not
|
|
1362
|
+
specified, the API will instruct the pipette to drop tips in different locations
|
|
1363
|
+
within the bin. Varying the tip drop location helps prevent tips
|
|
1364
|
+
from piling up in a single location.
|
|
1365
|
+
|
|
1366
|
+
Starting with API version 2.18, the API will only vary the tip drop location if
|
|
1367
|
+
``location`` is not specified. Specifying a ``TrashBin`` as the ``location``
|
|
1368
|
+
behaves the same as specifying :py:meth:`.TrashBin.top`, which is a fixed position.
|
|
1369
|
+
|
|
1370
|
+
:param location:
|
|
1371
|
+
Where to drop the tip.
|
|
1372
|
+
|
|
1373
|
+
.. versionchanged:: 2.16
|
|
1374
|
+
Accepts ``TrashBin`` and ``WasteChute`` values.
|
|
1375
|
+
|
|
1376
|
+
:type location:
|
|
1377
|
+
:py:class:`~.types.Location` or :py:class:`.Well` or ``None``
|
|
1378
|
+
:param home_after:
|
|
1379
|
+
Whether to home the pipette's plunger after dropping the tip. If not
|
|
1380
|
+
specified, defaults to ``True`` on an OT-2.
|
|
1381
|
+
|
|
1382
|
+
When ``False``, the pipette does not home its plunger. This can save a few
|
|
1383
|
+
seconds, but is not recommended. Homing helps the robot track the pipette's
|
|
1384
|
+
position.
|
|
1385
|
+
|
|
1386
|
+
:returns: This instance.
|
|
1387
|
+
"""
|
|
1388
|
+
alternate_drop_location: bool = False
|
|
1389
|
+
if location is None:
|
|
1390
|
+
trash_container = self.trash_container
|
|
1391
|
+
if self.api_version >= _DROP_TIP_LOCATION_ALTERNATING_ADDED_IN:
|
|
1392
|
+
alternate_drop_location = True
|
|
1393
|
+
if isinstance(trash_container, labware.Labware):
|
|
1394
|
+
well = trash_container.wells()[0]
|
|
1395
|
+
else: # implicit drop tip in disposal location, not well
|
|
1396
|
+
with publisher.publish_context(
|
|
1397
|
+
broker=self.broker,
|
|
1398
|
+
command=cmds.drop_tip_in_disposal_location(
|
|
1399
|
+
instrument=self, location=trash_container
|
|
1400
|
+
),
|
|
1401
|
+
):
|
|
1402
|
+
self._core.drop_tip_in_disposal_location(
|
|
1403
|
+
trash_container,
|
|
1404
|
+
home_after=home_after,
|
|
1405
|
+
alternate_tip_drop=True,
|
|
1406
|
+
)
|
|
1407
|
+
return self
|
|
1408
|
+
|
|
1409
|
+
elif isinstance(location, labware.Well):
|
|
1410
|
+
well = location
|
|
1411
|
+
location = None
|
|
1412
|
+
|
|
1413
|
+
elif isinstance(location, types.Location):
|
|
1414
|
+
_, maybe_well = location.labware.get_parent_labware_and_well()
|
|
1415
|
+
|
|
1416
|
+
if maybe_well is None:
|
|
1417
|
+
raise TypeError(
|
|
1418
|
+
"If a location is specified as a `types.Location`"
|
|
1419
|
+
" (for instance, as the result of a call to `Well.top()`),"
|
|
1420
|
+
" it must be a location relative to a well,"
|
|
1421
|
+
" since that is where a tip is dropped."
|
|
1422
|
+
f" However, the given location refers to {location.labware}"
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
well = maybe_well
|
|
1426
|
+
|
|
1427
|
+
elif isinstance(location, (TrashBin, WasteChute)):
|
|
1428
|
+
# In 2.16 and 2.17, we would always automatically use automatic alternate tip drop locations regardless
|
|
1429
|
+
# of whether you explicitly passed the disposal location as a location or if none was provided. Now, in
|
|
1430
|
+
# 2.18 and moving forward, passing it in will bypass the automatic behavior and instead go to the set
|
|
1431
|
+
# offset or the XY center if none is provided.
|
|
1432
|
+
if self.api_version < _DISPOSAL_LOCATION_OFFSET_ADDED_IN:
|
|
1433
|
+
alternate_drop_location = True
|
|
1434
|
+
with publisher.publish_context(
|
|
1435
|
+
broker=self.broker,
|
|
1436
|
+
command=cmds.drop_tip_in_disposal_location(
|
|
1437
|
+
instrument=self, location=location
|
|
1438
|
+
),
|
|
1439
|
+
):
|
|
1440
|
+
self._core.drop_tip_in_disposal_location(
|
|
1441
|
+
location,
|
|
1442
|
+
home_after=home_after,
|
|
1443
|
+
alternate_tip_drop=alternate_drop_location,
|
|
1444
|
+
)
|
|
1445
|
+
return self
|
|
1446
|
+
|
|
1447
|
+
else:
|
|
1448
|
+
raise TypeError(
|
|
1449
|
+
"If specified, location should be an instance of"
|
|
1450
|
+
" `types.Location` (e.g. the return value from `Well.top()`)"
|
|
1451
|
+
" or `Well` (e.g. `tiprack.wells()[0]`)."
|
|
1452
|
+
f" However, it is {location}"
|
|
1453
|
+
)
|
|
1454
|
+
|
|
1455
|
+
with publisher.publish_context(
|
|
1456
|
+
broker=self.broker,
|
|
1457
|
+
command=cmds.drop_tip(instrument=self, location=well),
|
|
1458
|
+
):
|
|
1459
|
+
self._core.drop_tip(
|
|
1460
|
+
location=location,
|
|
1461
|
+
well_core=well._core,
|
|
1462
|
+
home_after=home_after,
|
|
1463
|
+
alternate_drop_location=alternate_drop_location,
|
|
1464
|
+
)
|
|
1465
|
+
|
|
1466
|
+
return self
|
|
1467
|
+
|
|
1468
|
+
@requires_version(2, 0)
|
|
1469
|
+
def home(self) -> InstrumentContext:
|
|
1470
|
+
"""Home the robot.
|
|
1471
|
+
|
|
1472
|
+
See :ref:`utility-homing`.
|
|
1473
|
+
|
|
1474
|
+
:returns: This instance.
|
|
1475
|
+
"""
|
|
1476
|
+
|
|
1477
|
+
mount_name = self._core.get_mount().name.lower()
|
|
1478
|
+
|
|
1479
|
+
with publisher.publish_context(
|
|
1480
|
+
broker=self.broker, command=cmds.home(mount_name)
|
|
1481
|
+
):
|
|
1482
|
+
self._core.home()
|
|
1483
|
+
|
|
1484
|
+
return self
|
|
1485
|
+
|
|
1486
|
+
@requires_version(2, 0)
|
|
1487
|
+
def home_plunger(self) -> InstrumentContext:
|
|
1488
|
+
"""Home the plunger associated with this mount.
|
|
1489
|
+
|
|
1490
|
+
:returns: This instance.
|
|
1491
|
+
"""
|
|
1492
|
+
self._core.home_plunger()
|
|
1493
|
+
return self
|
|
1494
|
+
|
|
1495
|
+
@publisher.publish(command=cmds.distribute)
|
|
1496
|
+
@requires_version(2, 0)
|
|
1497
|
+
def distribute(
|
|
1498
|
+
self,
|
|
1499
|
+
volume: Union[float, Sequence[float]],
|
|
1500
|
+
source: labware.Well,
|
|
1501
|
+
dest: List[labware.Well],
|
|
1502
|
+
*args: Any,
|
|
1503
|
+
**kwargs: Any,
|
|
1504
|
+
) -> InstrumentContext:
|
|
1505
|
+
"""
|
|
1506
|
+
Move a volume of liquid from one source to multiple destinations.
|
|
1507
|
+
|
|
1508
|
+
:param volume: The amount, in µL, to dispense into each destination well.
|
|
1509
|
+
:param source: A single well to aspirate liquid from.
|
|
1510
|
+
:param dest: A list of wells to dispense liquid into.
|
|
1511
|
+
:param kwargs: See :py:meth:`transfer` and the :ref:`complex_params` page.
|
|
1512
|
+
Some parameters behave differently than when transferring.
|
|
1513
|
+
|
|
1514
|
+
- ``disposal_volume`` aspirates additional liquid to improve the accuracy
|
|
1515
|
+
of each dispense. Defaults to the minimum volume of the pipette. See
|
|
1516
|
+
:ref:`param-disposal-volume` for details.
|
|
1517
|
+
|
|
1518
|
+
- ``mix_after`` is ignored.
|
|
1519
|
+
|
|
1520
|
+
|
|
1521
|
+
:returns: This instance.
|
|
1522
|
+
"""
|
|
1523
|
+
_log.debug("Distributing {} from {} to {}".format(volume, source, dest))
|
|
1524
|
+
kwargs["mode"] = "distribute"
|
|
1525
|
+
kwargs["disposal_volume"] = kwargs.get("disposal_volume", self.min_volume)
|
|
1526
|
+
kwargs["mix_after"] = (0, 0)
|
|
1527
|
+
blowout_location = kwargs.get("blowout_location")
|
|
1528
|
+
instrument.validate_blowout_location(
|
|
1529
|
+
self.api_version, "distribute", blowout_location
|
|
1530
|
+
)
|
|
1531
|
+
|
|
1532
|
+
return self.transfer(volume, source, dest, **kwargs)
|
|
1533
|
+
|
|
1534
|
+
@publisher.publish(command=cmds.consolidate)
|
|
1535
|
+
@requires_version(2, 0)
|
|
1536
|
+
def consolidate(
|
|
1537
|
+
self,
|
|
1538
|
+
volume: Union[float, Sequence[float]],
|
|
1539
|
+
source: List[labware.Well],
|
|
1540
|
+
dest: labware.Well,
|
|
1541
|
+
*args: Any,
|
|
1542
|
+
**kwargs: Any,
|
|
1543
|
+
) -> InstrumentContext:
|
|
1544
|
+
"""
|
|
1545
|
+
Move liquid from multiple source wells to a single destination well.
|
|
1546
|
+
|
|
1547
|
+
:param volume: The amount, in µL, to aspirate from each source well.
|
|
1548
|
+
:param source: A list of wells to aspirate liquid from.
|
|
1549
|
+
:param dest: A single well to dispense liquid into.
|
|
1550
|
+
:param kwargs: See :py:meth:`transfer` and the :ref:`complex_params` page.
|
|
1551
|
+
Some parameters behave differently than when transferring.
|
|
1552
|
+
``disposal_volume`` and ``mix_before`` are ignored.
|
|
1553
|
+
:returns: This instance.
|
|
1554
|
+
"""
|
|
1555
|
+
_log.debug("Consolidate {} from {} to {}".format(volume, source, dest))
|
|
1556
|
+
kwargs["mode"] = "consolidate"
|
|
1557
|
+
kwargs["mix_before"] = (0, 0)
|
|
1558
|
+
kwargs["disposal_volume"] = 0
|
|
1559
|
+
blowout_location = kwargs.get("blowout_location")
|
|
1560
|
+
instrument.validate_blowout_location(
|
|
1561
|
+
self.api_version, "consolidate", blowout_location
|
|
1562
|
+
)
|
|
1563
|
+
|
|
1564
|
+
return self.transfer(volume, source, dest, **kwargs)
|
|
1565
|
+
|
|
1566
|
+
@publisher.publish(command=cmds.transfer)
|
|
1567
|
+
@requires_version(2, 0)
|
|
1568
|
+
def transfer( # noqa: C901
|
|
1569
|
+
self,
|
|
1570
|
+
volume: Union[float, Sequence[float]],
|
|
1571
|
+
source: AdvancedLiquidHandling,
|
|
1572
|
+
dest: AdvancedLiquidHandling,
|
|
1573
|
+
trash: bool = True,
|
|
1574
|
+
**kwargs: Any,
|
|
1575
|
+
) -> InstrumentContext:
|
|
1576
|
+
# source: Union[Well, List[Well], List[List[Well]]],
|
|
1577
|
+
# dest: Union[Well, List[Well], List[List[Well]]],
|
|
1578
|
+
# TODO: Reach consensus on kwargs
|
|
1579
|
+
# TODO: Decide if to use a disposal_volume
|
|
1580
|
+
# TODO: Accordingly decide if remaining liquid should be blown out to
|
|
1581
|
+
# TODO: ..trash or the original well.
|
|
1582
|
+
# TODO: What should happen if the user passes a non-first-row well
|
|
1583
|
+
# TODO: ..as src/dest *while using multichannel pipette?
|
|
1584
|
+
"""
|
|
1585
|
+
Move liquid from one well or group of wells to another.
|
|
1586
|
+
|
|
1587
|
+
Transfer is a higher-level command, incorporating other
|
|
1588
|
+
:py:class:`InstrumentContext` commands, like :py:meth:`aspirate` and
|
|
1589
|
+
:py:meth:`dispense`. It makes writing a protocol easier at the cost of
|
|
1590
|
+
specificity. See :ref:`v2-complex-commands` for details on how transfer and
|
|
1591
|
+
other complex commands perform their component steps.
|
|
1592
|
+
|
|
1593
|
+
:param volume: The amount, in µL, to aspirate from each source and dispense to
|
|
1594
|
+
each destination. If ``volume`` is a list, each amount will be
|
|
1595
|
+
used for the source and destination at the matching index. A list
|
|
1596
|
+
item of ``0`` will skip the corresponding wells entirely. See
|
|
1597
|
+
:ref:`complex-list-volumes` for details and examples.
|
|
1598
|
+
:param source: A single well or a list of wells to aspirate liquid from.
|
|
1599
|
+
:param dest: A single well or a list of wells to dispense liquid into.
|
|
1600
|
+
|
|
1601
|
+
:Keyword Arguments: Transfer accepts a number of optional parameters that give
|
|
1602
|
+
you greater control over the exact steps it performs. See
|
|
1603
|
+
:ref:`complex_params` or the links under each argument's entry below for
|
|
1604
|
+
additional details and examples.
|
|
1605
|
+
|
|
1606
|
+
* **new_tip** (*string*) --
|
|
1607
|
+
When to pick up and drop tips during the command. Defaults to ``"once"``.
|
|
1608
|
+
|
|
1609
|
+
- ``"once"``: Use one tip for the entire command.
|
|
1610
|
+
- ``"always"``: Use a new tip for each set of aspirate and dispense steps.
|
|
1611
|
+
- ``"never"``: Do not pick up or drop tips at all.
|
|
1612
|
+
|
|
1613
|
+
See :ref:`param-tip-handling` for details.
|
|
1614
|
+
|
|
1615
|
+
* **trash** (*boolean*) --
|
|
1616
|
+
If ``True`` (default), the pipette will drop tips in its
|
|
1617
|
+
:py:meth:`~.InstrumentContext.trash_container`.
|
|
1618
|
+
If ``False``, the pipette will return tips to their tip rack.
|
|
1619
|
+
|
|
1620
|
+
See :ref:`param-trash` for details.
|
|
1621
|
+
|
|
1622
|
+
* **touch_tip** (*boolean*) --
|
|
1623
|
+
If ``True``, perform a :py:meth:`touch_tip` following each
|
|
1624
|
+
:py:meth:`aspirate` and :py:meth:`dispense`. Defaults to ``False``.
|
|
1625
|
+
|
|
1626
|
+
See :ref:`param-touch-tip` for details.
|
|
1627
|
+
|
|
1628
|
+
* **blow_out** (*boolean*) --
|
|
1629
|
+
If ``True``, a :py:meth:`blow_out` will occur following each
|
|
1630
|
+
:py:meth:`dispense`, but only if the pipette has no liquid left
|
|
1631
|
+
in it. If ``False`` (default), the pipette will not blow out liquid.
|
|
1632
|
+
|
|
1633
|
+
See :ref:`param-blow-out` for details.
|
|
1634
|
+
|
|
1635
|
+
* **blowout_location** (*string*) --
|
|
1636
|
+
Accepts one of three string values: ``"trash"``, ``"source well"``, or
|
|
1637
|
+
``"destination well"``.
|
|
1638
|
+
|
|
1639
|
+
If ``blow_out`` is ``False`` (its default), this parameter is ignored.
|
|
1640
|
+
|
|
1641
|
+
If ``blow_out`` is ``True`` and this parameter is not set:
|
|
1642
|
+
|
|
1643
|
+
- Blow out into the trash, if the pipette is empty or only contains the
|
|
1644
|
+
disposal volume.
|
|
1645
|
+
|
|
1646
|
+
- Blow out into the source well, if the pipette otherwise contains liquid.
|
|
1647
|
+
|
|
1648
|
+
* **mix_before** (*tuple*) --
|
|
1649
|
+
Perform a :py:meth:`mix` before each :py:meth:`aspirate` during the
|
|
1650
|
+
transfer. The first value of the tuple is the number of repetitions, and
|
|
1651
|
+
the second value is the amount of liquid to mix in µL.
|
|
1652
|
+
|
|
1653
|
+
See :ref:`param-mix-before` for details.
|
|
1654
|
+
|
|
1655
|
+
* **mix_after** (*tuple*) --
|
|
1656
|
+
Perform a :py:meth:`mix` after each :py:meth:`dispense` during the
|
|
1657
|
+
transfer. The first value of the tuple is the number of repetitions, and
|
|
1658
|
+
the second value is the amount of liquid to mix in µL.
|
|
1659
|
+
|
|
1660
|
+
See :ref:`param-mix-after` for details.
|
|
1661
|
+
|
|
1662
|
+
* **disposal_volume** (*float*) --
|
|
1663
|
+
Transfer ignores the numeric value of this parameter. If set, the pipette
|
|
1664
|
+
will not aspirate additional liquid, but it will perform a very small blow
|
|
1665
|
+
out after each dispense.
|
|
1666
|
+
|
|
1667
|
+
See :ref:`param-disposal-volume` for details.
|
|
1668
|
+
|
|
1669
|
+
:returns: This instance.
|
|
1670
|
+
"""
|
|
1671
|
+
_log.debug("Transfer {} from {} to {}".format(volume, source, dest))
|
|
1672
|
+
|
|
1673
|
+
blowout_location = kwargs.get("blowout_location")
|
|
1674
|
+
instrument.validate_blowout_location(
|
|
1675
|
+
self.api_version, "transfer", blowout_location
|
|
1676
|
+
)
|
|
1677
|
+
|
|
1678
|
+
kwargs["mode"] = kwargs.get("mode", "transfer")
|
|
1679
|
+
|
|
1680
|
+
mix_strategy, mix_opts = mix_from_kwargs(kwargs)
|
|
1681
|
+
|
|
1682
|
+
if trash:
|
|
1683
|
+
drop_tip = v1_transfer.DropTipStrategy.TRASH
|
|
1684
|
+
else:
|
|
1685
|
+
drop_tip = v1_transfer.DropTipStrategy.RETURN
|
|
1686
|
+
|
|
1687
|
+
new_tip = kwargs.get("new_tip")
|
|
1688
|
+
if isinstance(new_tip, str):
|
|
1689
|
+
new_tip = types.TransferTipPolicy[new_tip.upper()]
|
|
1690
|
+
|
|
1691
|
+
blow_out = kwargs.get("blow_out")
|
|
1692
|
+
blow_out_strategy = None
|
|
1693
|
+
active_channels = (
|
|
1694
|
+
self.active_channels
|
|
1695
|
+
if self._api_version >= _PARTIAL_NOZZLE_CONFIGURATION_ADDED_IN
|
|
1696
|
+
else self.channels
|
|
1697
|
+
)
|
|
1698
|
+
nozzle_map = (
|
|
1699
|
+
self._core.get_nozzle_map()
|
|
1700
|
+
if self._api_version
|
|
1701
|
+
>= _PARTIAL_NOZZLE_CONFIGURATION_AUTOMATIC_TIP_TRACKING_IN
|
|
1702
|
+
else None
|
|
1703
|
+
)
|
|
1704
|
+
|
|
1705
|
+
if blow_out and not blowout_location:
|
|
1706
|
+
if self.current_volume:
|
|
1707
|
+
blow_out_strategy = v1_transfer.BlowOutStrategy.SOURCE
|
|
1708
|
+
else:
|
|
1709
|
+
blow_out_strategy = v1_transfer.BlowOutStrategy.TRASH
|
|
1710
|
+
elif blow_out and blowout_location:
|
|
1711
|
+
if blowout_location == "source well":
|
|
1712
|
+
blow_out_strategy = v1_transfer.BlowOutStrategy.SOURCE
|
|
1713
|
+
elif blowout_location == "destination well":
|
|
1714
|
+
blow_out_strategy = v1_transfer.BlowOutStrategy.DEST
|
|
1715
|
+
elif blowout_location == "trash":
|
|
1716
|
+
blow_out_strategy = v1_transfer.BlowOutStrategy.TRASH
|
|
1717
|
+
|
|
1718
|
+
if new_tip != types.TransferTipPolicy.NEVER:
|
|
1719
|
+
_, next_tip = labware.next_available_tip(
|
|
1720
|
+
self.starting_tip,
|
|
1721
|
+
self.tip_racks,
|
|
1722
|
+
active_channels,
|
|
1723
|
+
nozzle_map=nozzle_map,
|
|
1724
|
+
)
|
|
1725
|
+
max_volume = min(next_tip.max_volume, self.max_volume)
|
|
1726
|
+
else:
|
|
1727
|
+
max_volume = self._core.get_working_volume()
|
|
1728
|
+
|
|
1729
|
+
touch_tip = None
|
|
1730
|
+
if kwargs.get("touch_tip"):
|
|
1731
|
+
touch_tip = v1_transfer.TouchTipStrategy.ALWAYS
|
|
1732
|
+
|
|
1733
|
+
default_args = v1_transfer.Transfer()
|
|
1734
|
+
|
|
1735
|
+
disposal = kwargs.get("disposal_volume")
|
|
1736
|
+
if disposal is None:
|
|
1737
|
+
disposal = default_args.disposal_volume
|
|
1738
|
+
|
|
1739
|
+
air_gap = kwargs.get("air_gap", default_args.air_gap)
|
|
1740
|
+
if air_gap < 0 or air_gap >= max_volume:
|
|
1741
|
+
raise ValueError(
|
|
1742
|
+
"air_gap must be between 0uL and the pipette's expected "
|
|
1743
|
+
f"working volume, {max_volume}uL"
|
|
1744
|
+
)
|
|
1745
|
+
|
|
1746
|
+
transfer_args = v1_transfer.Transfer(
|
|
1747
|
+
new_tip=new_tip or default_args.new_tip,
|
|
1748
|
+
air_gap=air_gap,
|
|
1749
|
+
carryover=kwargs.get("carryover") or default_args.carryover,
|
|
1750
|
+
gradient_function=(
|
|
1751
|
+
kwargs.get("gradient_function") or default_args.gradient_function
|
|
1752
|
+
),
|
|
1753
|
+
disposal_volume=disposal,
|
|
1754
|
+
mix_strategy=mix_strategy,
|
|
1755
|
+
drop_tip_strategy=drop_tip,
|
|
1756
|
+
blow_out_strategy=blow_out_strategy or default_args.blow_out_strategy,
|
|
1757
|
+
touch_tip_strategy=(touch_tip or default_args.touch_tip_strategy),
|
|
1758
|
+
)
|
|
1759
|
+
transfer_options = v1_transfer.TransferOptions(
|
|
1760
|
+
transfer=transfer_args, mix=mix_opts
|
|
1761
|
+
)
|
|
1762
|
+
plan = v1_transfer.TransferPlan(
|
|
1763
|
+
volume,
|
|
1764
|
+
source,
|
|
1765
|
+
dest,
|
|
1766
|
+
self,
|
|
1767
|
+
max_volume,
|
|
1768
|
+
self.api_version,
|
|
1769
|
+
kwargs["mode"],
|
|
1770
|
+
transfer_options,
|
|
1771
|
+
)
|
|
1772
|
+
self._execute_transfer(plan)
|
|
1773
|
+
return self
|
|
1774
|
+
|
|
1775
|
+
def _execute_transfer(self, plan: v1_transfer.TransferPlan) -> None:
|
|
1776
|
+
for cmd in plan:
|
|
1777
|
+
getattr(self, cmd["method"])(*cmd["args"], **cmd["kwargs"])
|
|
1778
|
+
|
|
1779
|
+
@requires_version(2, 24)
|
|
1780
|
+
def transfer_with_liquid_class(
|
|
1781
|
+
self,
|
|
1782
|
+
liquid_class: LiquidClass,
|
|
1783
|
+
volume: float,
|
|
1784
|
+
source: Union[
|
|
1785
|
+
labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
|
|
1786
|
+
],
|
|
1787
|
+
dest: Union[
|
|
1788
|
+
labware.Well,
|
|
1789
|
+
Sequence[labware.Well],
|
|
1790
|
+
Sequence[Sequence[labware.Well]],
|
|
1791
|
+
TrashBin,
|
|
1792
|
+
WasteChute,
|
|
1793
|
+
],
|
|
1794
|
+
new_tip: TransferTipPolicyV2Type = "once",
|
|
1795
|
+
trash_location: Optional[
|
|
1796
|
+
Union[types.Location, labware.Well, TrashBin, WasteChute]
|
|
1797
|
+
] = None,
|
|
1798
|
+
return_tip: bool = False,
|
|
1799
|
+
group_wells: bool = True,
|
|
1800
|
+
keep_last_tip: Optional[bool] = None,
|
|
1801
|
+
tip_racks: Optional[List[labware.Labware]] = None,
|
|
1802
|
+
) -> InstrumentContext:
|
|
1803
|
+
"""Move a particular type of liquid from one well or group of wells to another.
|
|
1804
|
+
|
|
1805
|
+
:param liquid_class: The type of liquid to move. You must specify the liquid class,
|
|
1806
|
+
even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
|
|
1807
|
+
source contains.
|
|
1808
|
+
:type liquid_class: :py:class:`.LiquidClass`
|
|
1809
|
+
|
|
1810
|
+
:param volume: The amount, in µL, to aspirate from each source and dispense to
|
|
1811
|
+
each destination.
|
|
1812
|
+
:param source: A single well or a list of wells to aspirate liquid from.
|
|
1813
|
+
:param dest: A single well, list of wells, trash bin, or waste chute to dispense liquid into.
|
|
1814
|
+
:param new_tip: When to pick up and drop tips during the command.
|
|
1815
|
+
Defaults to ``"once"``.
|
|
1816
|
+
|
|
1817
|
+
- ``"once"``: Use one tip for the entire command.
|
|
1818
|
+
- ``"always"``: Use a new tip for each set of aspirate and dispense steps.
|
|
1819
|
+
- ``"per source"``: Use one tip for each source well, even if
|
|
1820
|
+
:ref:`tip refilling <complex-tip-refilling>` is required.
|
|
1821
|
+
- ``"per destination"``: Use one tip for each destination well, even if
|
|
1822
|
+
:ref:`tip refilling <complex-tip-refilling>` is required.
|
|
1823
|
+
- ``"never"``: Do not pick up or drop tips at all.
|
|
1824
|
+
|
|
1825
|
+
See :ref:`param-tip-handling` for details.
|
|
1826
|
+
|
|
1827
|
+
:param trash_location: A trash container, well, or other location to dispose of
|
|
1828
|
+
tips. Depending on the liquid class, the pipette may also blow out liquid here.
|
|
1829
|
+
If not specified, the pipette will dispose of tips in its :py:obj:`~.InstrumentContext.trash_container`.
|
|
1830
|
+
:param return_tip: Whether to drop used tips in their original locations
|
|
1831
|
+
in the tip rack, instead of the trash.
|
|
1832
|
+
:param group_wells: For multi-channel transfers only. If set to ``True``, group together contiguous wells
|
|
1833
|
+
given into a single transfer step, taking into account the tip configuration. If ``False``, target
|
|
1834
|
+
each well given with the primary nozzle. Defaults to ``True``.
|
|
1835
|
+
:param keep_last_tip: When ``True``, the pipette keeps the last tip used in the transfer attached. When
|
|
1836
|
+
``False``, the last tip will be dropped or returned. If not set, behavior depends on the value of
|
|
1837
|
+
``new_tip``. ``new_tip="never"`` keeps the tip, and all other values of ``new_tip`` drop or return the tip.
|
|
1838
|
+
:param tip_racks: A list of tip racks to pick up from for this command. If not provided, the pipette will pick
|
|
1839
|
+
up from its associated :py:obj:`.InstrumentContext.tip_racks`. Providing this argument does not change the
|
|
1840
|
+
value of ``InstrumentContext.tip_racks``.
|
|
1841
|
+
|
|
1842
|
+
.. versionchanged:: 2.25
|
|
1843
|
+
Added the ``tip_racks`` parameter.
|
|
1844
|
+
|
|
1845
|
+
"""
|
|
1846
|
+
if volume == 0.0:
|
|
1847
|
+
_log.info(
|
|
1848
|
+
f"Transfer of {liquid_class.name} specified with a volume of 0uL."
|
|
1849
|
+
f" Skipping."
|
|
1850
|
+
)
|
|
1851
|
+
return self
|
|
1852
|
+
|
|
1853
|
+
if (
|
|
1854
|
+
tip_racks is not None
|
|
1855
|
+
and self.api_version < _LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN
|
|
1856
|
+
):
|
|
1857
|
+
raise APIVersionError(
|
|
1858
|
+
api_element="tip_racks",
|
|
1859
|
+
until_version=f"{_LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN}",
|
|
1860
|
+
current_version=f"{self.api_version}",
|
|
1861
|
+
)
|
|
1862
|
+
|
|
1863
|
+
transfer_args = verify_and_normalize_transfer_args(
|
|
1864
|
+
source=source,
|
|
1865
|
+
dest=dest,
|
|
1866
|
+
tip_policy=new_tip,
|
|
1867
|
+
last_tip_well=self._get_current_tip_source_well(),
|
|
1868
|
+
tip_racks=tip_racks or self._tip_racks,
|
|
1869
|
+
nozzle_map=self._core.get_nozzle_map(),
|
|
1870
|
+
group_wells_for_multi_channel=group_wells,
|
|
1871
|
+
current_volume=self.current_volume,
|
|
1872
|
+
trash_location=(
|
|
1873
|
+
trash_location if trash_location is not None else self.trash_container
|
|
1874
|
+
),
|
|
1875
|
+
)
|
|
1876
|
+
verified_keep_last_tip = resolve_keep_last_tip(
|
|
1877
|
+
keep_last_tip, transfer_args.tip_policy
|
|
1878
|
+
)
|
|
1879
|
+
|
|
1880
|
+
verified_dest: Union[
|
|
1881
|
+
List[Tuple[types.Location, WellCore]], TrashBin, WasteChute
|
|
1882
|
+
]
|
|
1883
|
+
if isinstance(transfer_args.dest, (TrashBin, WasteChute)):
|
|
1884
|
+
verified_dest = transfer_args.dest
|
|
1885
|
+
else:
|
|
1886
|
+
if len(transfer_args.source) != len(transfer_args.dest):
|
|
1887
|
+
raise ValueError(
|
|
1888
|
+
"Sources and destinations should be of the same length in order to perform a transfer."
|
|
1889
|
+
" To transfer liquid from one source to many destinations, use 'distribute_liquid',"
|
|
1890
|
+
" to transfer liquid to one destination from many sources, use 'consolidate_liquid'."
|
|
1891
|
+
)
|
|
1892
|
+
verified_dest = [
|
|
1893
|
+
(types.Location(types.Point(), labware=well), well._core)
|
|
1894
|
+
for well in transfer_args.dest
|
|
1895
|
+
]
|
|
1896
|
+
|
|
1897
|
+
for tip_rack in transfer_args.tip_racks:
|
|
1898
|
+
instrument.validate_tiprack(self.name, tip_rack, _log)
|
|
1899
|
+
|
|
1900
|
+
with publisher.publish_context(
|
|
1901
|
+
broker=self.broker,
|
|
1902
|
+
command=cmds.transfer_with_liquid_class(
|
|
1903
|
+
instrument=self,
|
|
1904
|
+
liquid_class=liquid_class,
|
|
1905
|
+
volume=volume,
|
|
1906
|
+
source=source,
|
|
1907
|
+
destination=dest,
|
|
1908
|
+
),
|
|
1909
|
+
):
|
|
1910
|
+
self._core.transfer_with_liquid_class(
|
|
1911
|
+
liquid_class=liquid_class,
|
|
1912
|
+
volume=volume,
|
|
1913
|
+
source=[
|
|
1914
|
+
(types.Location(types.Point(), labware=well), well._core)
|
|
1915
|
+
for well in transfer_args.source
|
|
1916
|
+
],
|
|
1917
|
+
dest=verified_dest,
|
|
1918
|
+
new_tip=transfer_args.tip_policy,
|
|
1919
|
+
tip_racks=[
|
|
1920
|
+
(types.Location(types.Point(), labware=rack), rack._core)
|
|
1921
|
+
for rack in transfer_args.tip_racks
|
|
1922
|
+
],
|
|
1923
|
+
starting_tip=(
|
|
1924
|
+
self.starting_tip._core if self.starting_tip is not None else None
|
|
1925
|
+
),
|
|
1926
|
+
trash_location=transfer_args.trash_location,
|
|
1927
|
+
return_tip=return_tip,
|
|
1928
|
+
keep_last_tip=verified_keep_last_tip,
|
|
1929
|
+
)
|
|
1930
|
+
|
|
1931
|
+
return self
|
|
1932
|
+
|
|
1933
|
+
@requires_version(2, 24)
|
|
1934
|
+
def distribute_with_liquid_class(
|
|
1935
|
+
self,
|
|
1936
|
+
liquid_class: LiquidClass,
|
|
1937
|
+
volume: float,
|
|
1938
|
+
source: Union[labware.Well, Sequence[labware.Well]],
|
|
1939
|
+
dest: Union[
|
|
1940
|
+
labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
|
|
1941
|
+
],
|
|
1942
|
+
new_tip: TransferTipPolicyV2Type = "once",
|
|
1943
|
+
trash_location: Optional[
|
|
1944
|
+
Union[types.Location, labware.Well, TrashBin, WasteChute]
|
|
1945
|
+
] = None,
|
|
1946
|
+
return_tip: bool = False,
|
|
1947
|
+
group_wells: bool = True,
|
|
1948
|
+
keep_last_tip: Optional[bool] = None,
|
|
1949
|
+
tip_racks: Optional[List[labware.Labware]] = None,
|
|
1950
|
+
) -> InstrumentContext:
|
|
1951
|
+
"""
|
|
1952
|
+
Distribute a particular type of liquid from one well to a group of wells.
|
|
1953
|
+
|
|
1954
|
+
:param liquid_class: The type of liquid to move. You must specify the liquid class,
|
|
1955
|
+
even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
|
|
1956
|
+
source contains.
|
|
1957
|
+
:type liquid_class: :py:class:`.LiquidClass`
|
|
1958
|
+
|
|
1959
|
+
:param volume: The amount, in µL, to dispense to each destination.
|
|
1960
|
+
:param source: A single well for the pipette to target, or a group of wells to
|
|
1961
|
+
target in a single aspirate for a multi-channel pipette.
|
|
1962
|
+
:param dest: A list of wells to dispense liquid into.
|
|
1963
|
+
:param new_tip: When to pick up and drop tips during the command.
|
|
1964
|
+
Defaults to ``"once"``.
|
|
1965
|
+
|
|
1966
|
+
- ``"once"``: Use one tip for the entire command.
|
|
1967
|
+
- ``"always"``: Use a new tip before each aspirate.
|
|
1968
|
+
- ``"never"``: Do not pick up or drop tips at all.
|
|
1969
|
+
|
|
1970
|
+
See :ref:`param-tip-handling` for details.
|
|
1971
|
+
|
|
1972
|
+
:param trash_location: A trash container, well, or other location to dispose of
|
|
1973
|
+
tips. Depending on the liquid class, the pipette may also blow out liquid here.
|
|
1974
|
+
If not specified, the pipette will dispose of tips in its :py:obj:`~.InstrumentContext.trash_container`.
|
|
1975
|
+
:param return_tip: Whether to drop used tips in their original locations
|
|
1976
|
+
in the tip rack, instead of the trash.
|
|
1977
|
+
:param group_wells: For multi-channel transfers only. If set to ``True``, group together contiguous wells
|
|
1978
|
+
given into a single transfer step, taking into account the tip configuration. If ``False``, target
|
|
1979
|
+
each well given with the primary nozzle. Defaults to ``True``.
|
|
1980
|
+
:param keep_last_tip: When ``True``, the pipette keeps the last tip used in the distribute attached. When
|
|
1981
|
+
``False``, the last tip will be dropped or returned. If not set, behavior depends on the value of
|
|
1982
|
+
``new_tip``. ``new_tip="never"`` keeps the tip, and all other values of ``new_tip`` drop or return the tip.
|
|
1983
|
+
:param tip_racks: A list of tip racks to pick up from for this command. If not provided, the pipette will pick
|
|
1984
|
+
up from its associated :py:obj:`.InstrumentContext.tip_racks`. Providing this argument does not change the
|
|
1985
|
+
value of ``InstrumentContext.tip_racks``.
|
|
1986
|
+
|
|
1987
|
+
.. versionchanged:: 2.25
|
|
1988
|
+
Added the ``tip_racks`` parameter.
|
|
1989
|
+
|
|
1990
|
+
"""
|
|
1991
|
+
if volume == 0.0:
|
|
1992
|
+
_log.info(
|
|
1993
|
+
f"Distribution of {liquid_class.name} specified with a volume of 0uL."
|
|
1994
|
+
f" Skipping."
|
|
1995
|
+
)
|
|
1996
|
+
return self
|
|
1997
|
+
|
|
1998
|
+
if (
|
|
1999
|
+
tip_racks is not None
|
|
2000
|
+
and self.api_version < _LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN
|
|
2001
|
+
):
|
|
2002
|
+
raise APIVersionError(
|
|
2003
|
+
api_element="tip_racks",
|
|
2004
|
+
until_version=f"{_LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN}",
|
|
2005
|
+
current_version=f"{self.api_version}",
|
|
2006
|
+
)
|
|
2007
|
+
|
|
2008
|
+
transfer_args = verify_and_normalize_transfer_args(
|
|
2009
|
+
source=source,
|
|
2010
|
+
dest=dest,
|
|
2011
|
+
tip_policy=new_tip,
|
|
2012
|
+
last_tip_well=self._get_current_tip_source_well(),
|
|
2013
|
+
tip_racks=tip_racks or self._tip_racks,
|
|
2014
|
+
nozzle_map=self._core.get_nozzle_map(),
|
|
2015
|
+
group_wells_for_multi_channel=group_wells,
|
|
2016
|
+
current_volume=self.current_volume,
|
|
2017
|
+
trash_location=(
|
|
2018
|
+
trash_location if trash_location is not None else self.trash_container
|
|
2019
|
+
),
|
|
2020
|
+
)
|
|
2021
|
+
verified_keep_last_tip = resolve_keep_last_tip(
|
|
2022
|
+
keep_last_tip, transfer_args.tip_policy
|
|
2023
|
+
)
|
|
2024
|
+
|
|
2025
|
+
if isinstance(transfer_args.dest, (TrashBin, WasteChute)):
|
|
2026
|
+
raise ValueError(
|
|
2027
|
+
"distribute_with_liquid_class() does not support trash bin or waste chute"
|
|
2028
|
+
" as a destination."
|
|
2029
|
+
)
|
|
2030
|
+
if len(transfer_args.source) != 1:
|
|
2031
|
+
raise ValueError(
|
|
2032
|
+
f"Source should be a single well (or resolve to a single transfer for multi-channel) "
|
|
2033
|
+
f"but received {transfer_args.source}."
|
|
2034
|
+
)
|
|
2035
|
+
if transfer_args.tip_policy not in [
|
|
2036
|
+
TransferTipPolicyV2.ONCE,
|
|
2037
|
+
TransferTipPolicyV2.NEVER,
|
|
2038
|
+
TransferTipPolicyV2.ALWAYS,
|
|
2039
|
+
]:
|
|
2040
|
+
raise ValueError(
|
|
2041
|
+
f"Incompatible `new_tip` value of {new_tip}."
|
|
2042
|
+
f" `distribute_with_liquid_class()` only supports `new_tip` values of"
|
|
2043
|
+
f" 'once', 'never' and 'always'."
|
|
2044
|
+
)
|
|
2045
|
+
|
|
2046
|
+
for tip_rack in transfer_args.tip_racks:
|
|
2047
|
+
instrument.validate_tiprack(self.name, tip_rack, _log)
|
|
2048
|
+
|
|
2049
|
+
verified_source = transfer_args.source[0]
|
|
2050
|
+
with publisher.publish_context(
|
|
2051
|
+
broker=self.broker,
|
|
2052
|
+
command=cmds.distribute_with_liquid_class(
|
|
2053
|
+
instrument=self,
|
|
2054
|
+
liquid_class=liquid_class,
|
|
2055
|
+
volume=volume,
|
|
2056
|
+
source=source,
|
|
2057
|
+
destination=dest,
|
|
2058
|
+
),
|
|
2059
|
+
):
|
|
2060
|
+
self._core.distribute_with_liquid_class(
|
|
2061
|
+
liquid_class=liquid_class,
|
|
2062
|
+
volume=volume,
|
|
2063
|
+
source=(
|
|
2064
|
+
types.Location(types.Point(), labware=verified_source),
|
|
2065
|
+
verified_source._core,
|
|
2066
|
+
),
|
|
2067
|
+
dest=[
|
|
2068
|
+
(types.Location(types.Point(), labware=well), well._core)
|
|
2069
|
+
for well in transfer_args.dest
|
|
2070
|
+
],
|
|
2071
|
+
new_tip=transfer_args.tip_policy, # type: ignore[arg-type]
|
|
2072
|
+
tip_racks=[
|
|
2073
|
+
(types.Location(types.Point(), labware=rack), rack._core)
|
|
2074
|
+
for rack in transfer_args.tip_racks
|
|
2075
|
+
],
|
|
2076
|
+
starting_tip=(
|
|
2077
|
+
self.starting_tip._core if self.starting_tip is not None else None
|
|
2078
|
+
),
|
|
2079
|
+
trash_location=transfer_args.trash_location,
|
|
2080
|
+
return_tip=return_tip,
|
|
2081
|
+
keep_last_tip=verified_keep_last_tip,
|
|
2082
|
+
)
|
|
2083
|
+
|
|
2084
|
+
return self
|
|
2085
|
+
|
|
2086
|
+
@requires_version(2, 24)
|
|
2087
|
+
def consolidate_with_liquid_class(
|
|
2088
|
+
self,
|
|
2089
|
+
liquid_class: LiquidClass,
|
|
2090
|
+
volume: float,
|
|
2091
|
+
source: Union[
|
|
2092
|
+
labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]]
|
|
2093
|
+
],
|
|
2094
|
+
dest: Union[labware.Well, Sequence[labware.Well], TrashBin, WasteChute],
|
|
2095
|
+
new_tip: TransferTipPolicyV2Type = "once",
|
|
2096
|
+
trash_location: Optional[
|
|
2097
|
+
Union[types.Location, labware.Well, TrashBin, WasteChute]
|
|
2098
|
+
] = None,
|
|
2099
|
+
return_tip: bool = False,
|
|
2100
|
+
group_wells: bool = True,
|
|
2101
|
+
keep_last_tip: Optional[bool] = None,
|
|
2102
|
+
tip_racks: Optional[List[labware.Labware]] = None,
|
|
2103
|
+
) -> InstrumentContext:
|
|
2104
|
+
"""
|
|
2105
|
+
Consolidate a particular type of liquid from a group of wells to one well.
|
|
2106
|
+
|
|
2107
|
+
:param liquid_class: The type of liquid to move. You must specify the liquid class,
|
|
2108
|
+
even if you have used :py:meth:`.Labware.load_liquid` to indicate what liquid the
|
|
2109
|
+
source contains.
|
|
2110
|
+
:type liquid_class: :py:class:`.LiquidClass`
|
|
2111
|
+
|
|
2112
|
+
:param volume: The amount, in µL, to aspirate from each source well.
|
|
2113
|
+
:param source: A list of wells to aspirate liquid from.
|
|
2114
|
+
:param dest: A single well, list of wells, trash bin, or waste chute to dispense liquid into.
|
|
2115
|
+
Multiple wells can only be given for multi-channel pipette configurations, and
|
|
2116
|
+
must be able to be dispensed to in a single dispense.
|
|
2117
|
+
:param new_tip: When to pick up and drop tips during the command.
|
|
2118
|
+
Defaults to ``"once"``.
|
|
2119
|
+
|
|
2120
|
+
- ``"once"``: Use one tip for the entire command.
|
|
2121
|
+
- ``"always"``: Use a new tip after each aspirate and dispense, even when visiting the same source again.
|
|
2122
|
+
- ``"never"``: Do not pick up or drop tips at all.
|
|
2123
|
+
|
|
2124
|
+
See :ref:`param-tip-handling` for details.
|
|
2125
|
+
|
|
2126
|
+
:param trash_location: A trash container, well, or other location to dispose of
|
|
2127
|
+
tips. Depending on the liquid class, the pipette may also blow out liquid here.
|
|
2128
|
+
If not specified, the pipette will dispose of tips in its :py:obj:`~.InstrumentContext.trash_container`.
|
|
2129
|
+
:param return_tip: Whether to drop used tips in their original locations
|
|
2130
|
+
in the tip rack, instead of the trash.
|
|
2131
|
+
:param group_wells: For multi-channel transfers only. If set to ``True``, group together contiguous wells
|
|
2132
|
+
given into a single transfer step, taking into account the tip configuration. If ``False``, target
|
|
2133
|
+
each well given with the primary nozzle. Defaults to ``True``.
|
|
2134
|
+
:param keep_last_tip: When ``True``, the pipette keeps the last tip used in the consolidate attached. When
|
|
2135
|
+
``False``, the last tip will be dropped or returned. If not set, behavior depends on the value of
|
|
2136
|
+
``new_tip``. ``new_tip="never"`` keeps the tip, and all other values of ``new_tip`` drop or return the tip.
|
|
2137
|
+
:param tip_racks: A list of tip racks to pick up from for this command. If not provided, the pipette will pick
|
|
2138
|
+
up from its associated :py:obj:`.InstrumentContext.tip_racks`. Providing this argument does not change the
|
|
2139
|
+
value of ``InstrumentContext.tip_racks``.
|
|
2140
|
+
|
|
2141
|
+
.. versionchanged:: 2.25
|
|
2142
|
+
Added the ``tip_racks`` parameter.
|
|
2143
|
+
|
|
2144
|
+
"""
|
|
2145
|
+
if volume == 0.0:
|
|
2146
|
+
_log.info(
|
|
2147
|
+
f"Consolidation of {liquid_class.name} specified with a volume of 0uL."
|
|
2148
|
+
f" Skipping."
|
|
2149
|
+
)
|
|
2150
|
+
return self
|
|
2151
|
+
|
|
2152
|
+
if (
|
|
2153
|
+
tip_racks is not None
|
|
2154
|
+
and self.api_version < _LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN
|
|
2155
|
+
):
|
|
2156
|
+
raise APIVersionError(
|
|
2157
|
+
api_element="tip_racks",
|
|
2158
|
+
until_version=f"{_LIQUID_CLASS_TRANSFER_TIP_RACKS_ARG_ADDED_IN}",
|
|
2159
|
+
current_version=f"{self.api_version}",
|
|
2160
|
+
)
|
|
2161
|
+
|
|
2162
|
+
transfer_args = verify_and_normalize_transfer_args(
|
|
2163
|
+
source=source,
|
|
2164
|
+
dest=dest,
|
|
2165
|
+
tip_policy=new_tip,
|
|
2166
|
+
last_tip_well=self._get_current_tip_source_well(),
|
|
2167
|
+
tip_racks=tip_racks or self._tip_racks,
|
|
2168
|
+
nozzle_map=self._core.get_nozzle_map(),
|
|
2169
|
+
group_wells_for_multi_channel=group_wells,
|
|
2170
|
+
current_volume=self.current_volume,
|
|
2171
|
+
trash_location=(
|
|
2172
|
+
trash_location if trash_location is not None else self.trash_container
|
|
2173
|
+
),
|
|
2174
|
+
)
|
|
2175
|
+
verified_keep_last_tip = resolve_keep_last_tip(
|
|
2176
|
+
keep_last_tip, transfer_args.tip_policy
|
|
2177
|
+
)
|
|
2178
|
+
|
|
2179
|
+
verified_dest: Union[Tuple[types.Location, WellCore], TrashBin, WasteChute]
|
|
2180
|
+
if isinstance(transfer_args.dest, (TrashBin, WasteChute)):
|
|
2181
|
+
verified_dest = transfer_args.dest
|
|
2182
|
+
else:
|
|
2183
|
+
if len(transfer_args.dest) != 1:
|
|
2184
|
+
raise ValueError(
|
|
2185
|
+
f"Destination should be a single well (or resolve to a single transfer for multi-channel) "
|
|
2186
|
+
f"but received {transfer_args.dest}."
|
|
2187
|
+
)
|
|
2188
|
+
verified_dest = (
|
|
2189
|
+
types.Location(types.Point(), labware=transfer_args.dest[0]),
|
|
2190
|
+
transfer_args.dest[0]._core,
|
|
2191
|
+
)
|
|
2192
|
+
if transfer_args.tip_policy not in [
|
|
2193
|
+
TransferTipPolicyV2.ONCE,
|
|
2194
|
+
TransferTipPolicyV2.NEVER,
|
|
2195
|
+
TransferTipPolicyV2.ALWAYS,
|
|
2196
|
+
]:
|
|
2197
|
+
raise ValueError(
|
|
2198
|
+
f"Incompatible `new_tip` value of {new_tip}."
|
|
2199
|
+
f" `consolidate_with_liquid_class()` only supports `new_tip` values of"
|
|
2200
|
+
f" 'once', 'never' and 'always'."
|
|
2201
|
+
)
|
|
2202
|
+
|
|
2203
|
+
for tip_rack in transfer_args.tip_racks:
|
|
2204
|
+
instrument.validate_tiprack(self.name, tip_rack, _log)
|
|
2205
|
+
|
|
2206
|
+
with publisher.publish_context(
|
|
2207
|
+
broker=self.broker,
|
|
2208
|
+
command=cmds.consolidate_with_liquid_class(
|
|
2209
|
+
instrument=self,
|
|
2210
|
+
liquid_class=liquid_class,
|
|
2211
|
+
volume=volume,
|
|
2212
|
+
source=source,
|
|
2213
|
+
destination=dest,
|
|
2214
|
+
),
|
|
2215
|
+
):
|
|
2216
|
+
self._core.consolidate_with_liquid_class(
|
|
2217
|
+
liquid_class=liquid_class,
|
|
2218
|
+
volume=volume,
|
|
2219
|
+
source=[
|
|
2220
|
+
(types.Location(types.Point(), labware=well), well._core)
|
|
2221
|
+
for well in transfer_args.source
|
|
2222
|
+
],
|
|
2223
|
+
dest=verified_dest,
|
|
2224
|
+
new_tip=transfer_args.tip_policy, # type: ignore[arg-type]
|
|
2225
|
+
tip_racks=[
|
|
2226
|
+
(types.Location(types.Point(), labware=rack), rack._core)
|
|
2227
|
+
for rack in transfer_args.tip_racks
|
|
2228
|
+
],
|
|
2229
|
+
starting_tip=(
|
|
2230
|
+
self.starting_tip._core if self.starting_tip is not None else None
|
|
2231
|
+
),
|
|
2232
|
+
trash_location=transfer_args.trash_location,
|
|
2233
|
+
return_tip=return_tip,
|
|
2234
|
+
keep_last_tip=verified_keep_last_tip,
|
|
2235
|
+
)
|
|
2236
|
+
|
|
2237
|
+
return self
|
|
2238
|
+
|
|
2239
|
+
@requires_version(2, 0)
|
|
2240
|
+
def delay(self, *args: Any, **kwargs: Any) -> None:
|
|
2241
|
+
"""
|
|
2242
|
+
.. deprecated:: 2.0
|
|
2243
|
+
Use :py:obj:`ProtocolContext.delay` instead.
|
|
2244
|
+
This method does nothing.
|
|
2245
|
+
It will be removed from a future version of the Python Protocol API.
|
|
2246
|
+
"""
|
|
2247
|
+
if args or kwargs:
|
|
2248
|
+
# Former implementations of this method did not take any args, so users
|
|
2249
|
+
# would get a TypeError if they tried to call it like delay(minutes=10).
|
|
2250
|
+
# Without changing the ultimate behavior that such a call fails the
|
|
2251
|
+
# protocol, we can provide a more descriptive message as a courtesy.
|
|
2252
|
+
raise UnsupportedAPIError(
|
|
2253
|
+
message="InstrumentContext.delay() is not supported in Python Protocol API v2. Use ProtocolContext.delay() instead."
|
|
2254
|
+
)
|
|
2255
|
+
else:
|
|
2256
|
+
# Former implementations of this method, when called without any args,
|
|
2257
|
+
# called ProtocolContext.delay() with a duration of 0, which was
|
|
2258
|
+
# approximately a no-op.
|
|
2259
|
+
# Preserve that allowed way to call this method for the very remote chance
|
|
2260
|
+
# that a protocol out in the wild does it, for some reason.
|
|
2261
|
+
pass
|
|
2262
|
+
|
|
2263
|
+
@requires_version(2, 0)
|
|
2264
|
+
def move_to(
|
|
2265
|
+
self,
|
|
2266
|
+
location: Union[types.Location, TrashBin, WasteChute],
|
|
2267
|
+
force_direct: bool = False,
|
|
2268
|
+
minimum_z_height: Optional[float] = None,
|
|
2269
|
+
speed: Optional[float] = None,
|
|
2270
|
+
publish: bool = True,
|
|
2271
|
+
) -> InstrumentContext:
|
|
2272
|
+
"""Move the instrument.
|
|
2273
|
+
|
|
2274
|
+
See :ref:`move-to` for examples.
|
|
2275
|
+
|
|
2276
|
+
:param location: Where to move to.
|
|
2277
|
+
|
|
2278
|
+
.. versionchanged:: 2.16
|
|
2279
|
+
Accepts ``TrashBin`` and ``WasteChute`` values.
|
|
2280
|
+
|
|
2281
|
+
:type location: :py:class:`~.types.Location`
|
|
2282
|
+
:param force_direct: If ``True``, move directly to the destination without arc
|
|
2283
|
+
motion.
|
|
2284
|
+
|
|
2285
|
+
.. warning::
|
|
2286
|
+
Forcing direct motion can cause the pipette to crash
|
|
2287
|
+
into labware, modules, or other objects on the deck.
|
|
2288
|
+
|
|
2289
|
+
:param minimum_z_height: An amount, measured in mm, to raise the mid-arc height.
|
|
2290
|
+
The mid-arc height can't be lowered.
|
|
2291
|
+
:param speed: The speed at which to move. By default,
|
|
2292
|
+
:py:attr:`InstrumentContext.default_speed`. This controls the
|
|
2293
|
+
straight linear speed of the motion. To limit individual axis
|
|
2294
|
+
speeds, use :py:obj:`.ProtocolContext.max_speeds`.
|
|
2295
|
+
|
|
2296
|
+
:param publish: Whether to list this function call in the run preview.
|
|
2297
|
+
Default is ``True``.
|
|
2298
|
+
"""
|
|
2299
|
+
with ExitStack() as contexts:
|
|
2300
|
+
if isinstance(location, (TrashBin, WasteChute)):
|
|
2301
|
+
if publish:
|
|
2302
|
+
contexts.enter_context(
|
|
2303
|
+
publisher.publish_context(
|
|
2304
|
+
broker=self.broker,
|
|
2305
|
+
command=cmds.move_to_disposal_location(
|
|
2306
|
+
instrument=self, location=location
|
|
2307
|
+
),
|
|
2308
|
+
)
|
|
2309
|
+
)
|
|
2310
|
+
|
|
2311
|
+
self._core.move_to(
|
|
2312
|
+
location=location,
|
|
2313
|
+
well_core=None,
|
|
2314
|
+
force_direct=force_direct,
|
|
2315
|
+
minimum_z_height=minimum_z_height,
|
|
2316
|
+
speed=speed,
|
|
2317
|
+
check_for_movement_conflicts=False,
|
|
2318
|
+
)
|
|
2319
|
+
else:
|
|
2320
|
+
if publish:
|
|
2321
|
+
contexts.enter_context(
|
|
2322
|
+
publisher.publish_context(
|
|
2323
|
+
broker=self.broker,
|
|
2324
|
+
command=cmds.move_to(instrument=self, location=location),
|
|
2325
|
+
)
|
|
2326
|
+
)
|
|
2327
|
+
|
|
2328
|
+
_, well = location.labware.get_parent_labware_and_well()
|
|
2329
|
+
|
|
2330
|
+
self._core.move_to(
|
|
2331
|
+
location=location,
|
|
2332
|
+
well_core=well._core if well is not None else None,
|
|
2333
|
+
force_direct=force_direct,
|
|
2334
|
+
minimum_z_height=minimum_z_height,
|
|
2335
|
+
speed=speed,
|
|
2336
|
+
check_for_movement_conflicts=False,
|
|
2337
|
+
)
|
|
2338
|
+
|
|
2339
|
+
return self
|
|
2340
|
+
|
|
2341
|
+
@requires_version(2, 23)
|
|
2342
|
+
def resin_tip_seal(
|
|
2343
|
+
self,
|
|
2344
|
+
location: Union[labware.Well, labware.Labware],
|
|
2345
|
+
) -> InstrumentContext:
|
|
2346
|
+
"""Seal resin tips onto the pipette.
|
|
2347
|
+
|
|
2348
|
+
The location provided should contain resin tips. The pipette will attach itself
|
|
2349
|
+
to the resin tips but does not check any tip presence sensors. Before the pipette
|
|
2350
|
+
seals to the tips, the plunger will rise to the top of its working range so that
|
|
2351
|
+
it can perform a :py:func:`resin_tip_dispense` immediately.
|
|
2352
|
+
|
|
2353
|
+
:param location: A location containing resin tips, must be a Labware or a Well.
|
|
2354
|
+
:type location: :py:class:`~.types.Location`
|
|
2355
|
+
"""
|
|
2356
|
+
if isinstance(location, labware.Labware):
|
|
2357
|
+
well = location.wells()[0]
|
|
2358
|
+
else:
|
|
2359
|
+
well = location
|
|
2360
|
+
|
|
2361
|
+
with publisher.publish_context(
|
|
2362
|
+
broker=self.broker,
|
|
2363
|
+
command=cmds.seal(
|
|
2364
|
+
instrument=self,
|
|
2365
|
+
location=well,
|
|
2366
|
+
),
|
|
2367
|
+
):
|
|
2368
|
+
self._core.resin_tip_seal(
|
|
2369
|
+
location=well.top(), well_core=well._core, in_place=False
|
|
2370
|
+
)
|
|
2371
|
+
return self
|
|
2372
|
+
|
|
2373
|
+
@requires_version(2, 23)
|
|
2374
|
+
def resin_tip_unseal(
|
|
2375
|
+
self,
|
|
2376
|
+
location: Union[labware.Well, labware.Labware],
|
|
2377
|
+
) -> InstrumentContext:
|
|
2378
|
+
"""Release resin tips from the pipette.
|
|
2379
|
+
|
|
2380
|
+
The location provided should be a valid location to drop resin tips.
|
|
2381
|
+
|
|
2382
|
+
:param location: A location containing that can accept tips.
|
|
2383
|
+
|
|
2384
|
+
:type location: :py:class:`~.types.Location`
|
|
2385
|
+
|
|
2386
|
+
:param home_after:
|
|
2387
|
+
Whether to home the pipette after dropping the tip. If not specified
|
|
2388
|
+
defaults to ``True`` on a Flex. The plunger will not home on an unseal.
|
|
2389
|
+
|
|
2390
|
+
When ``False``, the pipette does not home its plunger. This can save a few
|
|
2391
|
+
seconds, but is not recommended. Homing helps the robot track the pipette's
|
|
2392
|
+
position.
|
|
2393
|
+
|
|
2394
|
+
"""
|
|
2395
|
+
if isinstance(location, labware.Labware):
|
|
2396
|
+
well = location.wells()[0]
|
|
2397
|
+
else:
|
|
2398
|
+
well = location
|
|
2399
|
+
|
|
2400
|
+
with publisher.publish_context(
|
|
2401
|
+
broker=self.broker,
|
|
2402
|
+
command=cmds.unseal(
|
|
2403
|
+
instrument=self,
|
|
2404
|
+
location=well,
|
|
2405
|
+
),
|
|
2406
|
+
):
|
|
2407
|
+
self._core.resin_tip_unseal(location=None, well_core=well._core)
|
|
2408
|
+
|
|
2409
|
+
return self
|
|
2410
|
+
|
|
2411
|
+
@requires_version(2, 23)
|
|
2412
|
+
def resin_tip_dispense(
|
|
2413
|
+
self,
|
|
2414
|
+
location: types.Location,
|
|
2415
|
+
volume: Optional[float] = None,
|
|
2416
|
+
rate: Optional[float] = None,
|
|
2417
|
+
) -> InstrumentContext:
|
|
2418
|
+
"""Push liquid out of resin tips that are currently sealed to a pipette.
|
|
2419
|
+
|
|
2420
|
+
The volume and rate parameters for this function control the motion of the plunger
|
|
2421
|
+
to create a desired pressure profile inside the pipette chamber. Unlike a regular
|
|
2422
|
+
dispense action, the volume and rate do not correspond to liquid volume or flow rate
|
|
2423
|
+
dispensed from the resin tips. Select your values for volume and flow rate based on
|
|
2424
|
+
experimentation with the resin tips to create a pressure profile.
|
|
2425
|
+
|
|
2426
|
+
The common way to use this function is as follows:
|
|
2427
|
+
|
|
2428
|
+
#. Seal resin tips to the pipette using :py:meth:`InstrumentContext.resin_tip_seal`.
|
|
2429
|
+
|
|
2430
|
+
#. Use :py:meth:`InstrumentContext.resin_tip_dispense` to displace an experimentally
|
|
2431
|
+
derived volume at an experimentally derived rate to create an experimentally derived
|
|
2432
|
+
target pressure inside the pipette.
|
|
2433
|
+
|
|
2434
|
+
#. Use :py:meth:`ProtocolContext.delay` to wait an experimentally derived amount of
|
|
2435
|
+
time for the pressure inside the pipette to push liquid into and through the resin tip
|
|
2436
|
+
and out the other side.
|
|
2437
|
+
|
|
2438
|
+
#. As liquid passes through the resin tip, the pressure inside the pipette will
|
|
2439
|
+
fall. If not all liquid has been dispensed from the resin tip, repeat steps 2
|
|
2440
|
+
and 3.
|
|
2441
|
+
|
|
2442
|
+
#. Unseal resin tips from the pipette using :py:meth:`InstrumentContext.resin_tip_unseal`.
|
|
2443
|
+
|
|
2444
|
+
Flex pipette pressure sensors will raise an overpressure when a differential pressure
|
|
2445
|
+
inside the pipette chamber above sensor limits is detected. You may need to disable the
|
|
2446
|
+
pressure sensor to create the required pressure profile.
|
|
2447
|
+
|
|
2448
|
+
.. warning::
|
|
2449
|
+
Building excessive pressure inside the pipette chamber (significantly above the sensor
|
|
2450
|
+
limit) with the pressure sensors disabled can damage the pipette.
|
|
2451
|
+
|
|
2452
|
+
|
|
2453
|
+
:param location: Tells the robot where to dispense.
|
|
2454
|
+
:type location: :py:class:`~.types.Location`
|
|
2455
|
+
|
|
2456
|
+
:param volume: The volume that the plunger should displace, in µL. Does not directly relate
|
|
2457
|
+
to the volume of liquid that will be dispensed.
|
|
2458
|
+
:type volume: float
|
|
2459
|
+
|
|
2460
|
+
:param rate: How quickly the plunger moves to displace the commanded volume, in µL/s. This rate does not directly relate to
|
|
2461
|
+
the flow rate of liquid out of the resin tip.
|
|
2462
|
+
|
|
2463
|
+
Defaults to ``10.0`` µL/s.
|
|
2464
|
+
:type rate: float
|
|
2465
|
+
"""
|
|
2466
|
+
well: Optional[labware.Well] = None
|
|
2467
|
+
last_location = self._get_last_location_by_api_version()
|
|
2468
|
+
|
|
2469
|
+
try:
|
|
2470
|
+
target = validation.validate_location(
|
|
2471
|
+
location=location, last_location=last_location
|
|
2472
|
+
)
|
|
2473
|
+
except validation.NoLocationError as e:
|
|
2474
|
+
raise RuntimeError(
|
|
2475
|
+
"If dispense is called without an explicit location, another"
|
|
2476
|
+
" method that moves to a location (such as move_to or "
|
|
2477
|
+
"aspirate) must previously have been called so the robot "
|
|
2478
|
+
"knows where it is."
|
|
2479
|
+
) from e
|
|
2480
|
+
|
|
2481
|
+
if isinstance(target, validation.WellTarget):
|
|
2482
|
+
well = target.well
|
|
2483
|
+
if target.location:
|
|
2484
|
+
move_to_location = target.location
|
|
2485
|
+
elif well.parent._core.is_fixed_trash():
|
|
2486
|
+
move_to_location = target.well.top()
|
|
2487
|
+
else:
|
|
2488
|
+
move_to_location = target.well.bottom(
|
|
2489
|
+
z=self._well_bottom_clearances.dispense
|
|
2490
|
+
)
|
|
2491
|
+
else:
|
|
2492
|
+
raise RuntimeError(
|
|
2493
|
+
"A well must be specified when using `resin_tip_dispense`."
|
|
2494
|
+
)
|
|
2495
|
+
|
|
2496
|
+
with publisher.publish_context(
|
|
2497
|
+
broker=self.broker,
|
|
2498
|
+
command=cmds.resin_tip_dispense(
|
|
2499
|
+
instrument=self,
|
|
2500
|
+
flow_rate=rate,
|
|
2501
|
+
),
|
|
2502
|
+
):
|
|
2503
|
+
self._core.resin_tip_dispense(
|
|
2504
|
+
move_to_location,
|
|
2505
|
+
well_core=well._core,
|
|
2506
|
+
volume=volume,
|
|
2507
|
+
flow_rate=rate,
|
|
2508
|
+
)
|
|
2509
|
+
return self
|
|
2510
|
+
|
|
2511
|
+
@requires_version(2, 18)
|
|
2512
|
+
def _retract(
|
|
2513
|
+
self,
|
|
2514
|
+
) -> None:
|
|
2515
|
+
self._core.retract()
|
|
2516
|
+
|
|
2517
|
+
@property
|
|
2518
|
+
@requires_version(2, 0)
|
|
2519
|
+
def mount(self) -> str:
|
|
2520
|
+
"""
|
|
2521
|
+
Return the name of the mount the pipette is attached to.
|
|
2522
|
+
|
|
2523
|
+
The possible names are ``"left"`` and ``"right"``.
|
|
2524
|
+
"""
|
|
2525
|
+
return self._core.get_mount().name.lower()
|
|
2526
|
+
|
|
2527
|
+
@property
|
|
2528
|
+
@requires_version(2, 0)
|
|
2529
|
+
def speed(self) -> "PlungerSpeeds":
|
|
2530
|
+
"""The speeds (in mm/s) configured for the pipette plunger.
|
|
2531
|
+
|
|
2532
|
+
This is an object with attributes ``aspirate``, ``dispense``, and ``blow_out``
|
|
2533
|
+
holding the plunger speeds for the corresponding operation.
|
|
2534
|
+
|
|
2535
|
+
.. note::
|
|
2536
|
+
Setting values of :py:attr:`flow_rate` will override the values in
|
|
2537
|
+
:py:attr:`speed`.
|
|
2538
|
+
|
|
2539
|
+
.. versionchanged:: 2.14
|
|
2540
|
+
This property has been removed because it's fundamentally misaligned with
|
|
2541
|
+
the step-wise nature of a pipette's plunger speed configuration. Use
|
|
2542
|
+
:py:attr:`.flow_rate` instead.
|
|
2543
|
+
"""
|
|
2544
|
+
if self._api_version >= ENGINE_CORE_API_VERSION:
|
|
2545
|
+
raise UnsupportedAPIError(
|
|
2546
|
+
message="InstrumentContext.speed has been removed. Use InstrumentContext.flow_rate, instead."
|
|
2547
|
+
)
|
|
2548
|
+
|
|
2549
|
+
# TODO(mc, 2023-02-13): this assert should be enough for mypy
|
|
2550
|
+
# investigate if upgrading mypy allows the `cast` to be removed
|
|
2551
|
+
assert isinstance(self._core, LegacyInstrumentCore)
|
|
2552
|
+
return cast(LegacyInstrumentCore, self._core).get_speed()
|
|
2553
|
+
|
|
2554
|
+
@property
|
|
2555
|
+
@requires_version(2, 0)
|
|
2556
|
+
def flow_rate(self) -> "FlowRates":
|
|
2557
|
+
"""The speeds, in µL/s, configured for the pipette.
|
|
2558
|
+
|
|
2559
|
+
See :ref:`new-plunger-flow-rates`.
|
|
2560
|
+
|
|
2561
|
+
This is an object with attributes ``aspirate``, ``dispense``, and ``blow_out``
|
|
2562
|
+
holding the flow rate for the corresponding operation.
|
|
2563
|
+
|
|
2564
|
+
.. note::
|
|
2565
|
+
Setting values of :py:attr:`speed`, which is deprecated, will override the
|
|
2566
|
+
values in :py:attr:`flow_rate`.
|
|
2567
|
+
|
|
2568
|
+
"""
|
|
2569
|
+
return self._core.get_flow_rate()
|
|
2570
|
+
|
|
2571
|
+
@property
|
|
2572
|
+
@requires_version(2, 0)
|
|
2573
|
+
def type(self) -> str:
|
|
2574
|
+
"""``'single'`` if this is a 1-channel pipette, or ``'multi'`` otherwise.
|
|
2575
|
+
|
|
2576
|
+
See also :py:obj:`.channels`, which can distinguish between 8-channel and 96-channel
|
|
2577
|
+
pipettes.
|
|
2578
|
+
"""
|
|
2579
|
+
if self.channels == 1:
|
|
2580
|
+
return "single"
|
|
2581
|
+
else:
|
|
2582
|
+
return "multi"
|
|
2583
|
+
|
|
2584
|
+
@property
|
|
2585
|
+
@requires_version(2, 0)
|
|
2586
|
+
def tip_racks(self) -> List[labware.Labware]:
|
|
2587
|
+
"""
|
|
2588
|
+
The tip racks that have been linked to this pipette.
|
|
2589
|
+
|
|
2590
|
+
This is the property used to determine which tips to pick up next when calling
|
|
2591
|
+
:py:meth:`pick_up_tip` without arguments. See :ref:`basic-tip-pickup`.
|
|
2592
|
+
"""
|
|
2593
|
+
return self._tip_racks
|
|
2594
|
+
|
|
2595
|
+
@tip_racks.setter
|
|
2596
|
+
def tip_racks(self, racks: List[labware.Labware]) -> None:
|
|
2597
|
+
self._tip_racks = racks
|
|
2598
|
+
|
|
2599
|
+
@property
|
|
2600
|
+
@requires_version(2, 20)
|
|
2601
|
+
def liquid_presence_detection(self) -> bool:
|
|
2602
|
+
"""
|
|
2603
|
+
Whether the pipette will perform automatic liquid presence detection.
|
|
2604
|
+
|
|
2605
|
+
When ``True``, the pipette will check for liquid on every aspiration.
|
|
2606
|
+
Defaults to ``False``. See :ref:`lpd`.
|
|
2607
|
+
"""
|
|
2608
|
+
return self._core.get_liquid_presence_detection()
|
|
2609
|
+
|
|
2610
|
+
@liquid_presence_detection.setter
|
|
2611
|
+
@requires_version(2, 20)
|
|
2612
|
+
def liquid_presence_detection(self, enable: bool) -> None:
|
|
2613
|
+
if enable:
|
|
2614
|
+
self._raise_if_pressure_not_supported_by_pipette()
|
|
2615
|
+
self._core.set_liquid_presence_detection(enable)
|
|
2616
|
+
|
|
2617
|
+
@property
|
|
2618
|
+
@requires_version(2, 0)
|
|
2619
|
+
def trash_container(self) -> Union[labware.Labware, TrashBin, WasteChute]:
|
|
2620
|
+
"""The trash container associated with this pipette.
|
|
2621
|
+
|
|
2622
|
+
This is the property used to determine where to drop tips and blow out liquids
|
|
2623
|
+
when calling :py:meth:`drop_tip` or :py:meth:`blow_out` without arguments.
|
|
2624
|
+
|
|
2625
|
+
You can set this to a :py:obj:`Labware`, :py:class:`.TrashBin`, or :py:class:`.WasteChute`.
|
|
2626
|
+
|
|
2627
|
+
The default value depends on the robot type and API version:
|
|
2628
|
+
|
|
2629
|
+
- :py:obj:`ProtocolContext.fixed_trash`, if it exists.
|
|
2630
|
+
- Otherwise, the first item previously loaded with
|
|
2631
|
+
:py:obj:`ProtocolContext.load_trash_bin()` or
|
|
2632
|
+
:py:obj:`ProtocolContext.load_waste_chute()`.
|
|
2633
|
+
|
|
2634
|
+
.. versionchanged:: 2.16
|
|
2635
|
+
Added support for ``TrashBin`` and ``WasteChute`` objects.
|
|
2636
|
+
"""
|
|
2637
|
+
if self._user_specified_trash is None:
|
|
2638
|
+
disposal_locations = self._protocol_core.get_disposal_locations()
|
|
2639
|
+
if len(disposal_locations) == 0:
|
|
2640
|
+
raise NoTrashDefinedError(
|
|
2641
|
+
"No trash container has been defined in this protocol."
|
|
2642
|
+
)
|
|
2643
|
+
return disposal_locations[0]
|
|
2644
|
+
return self._user_specified_trash
|
|
2645
|
+
|
|
2646
|
+
@trash_container.setter
|
|
2647
|
+
def trash_container(
|
|
2648
|
+
self, trash: Union[labware.Labware, TrashBin, WasteChute]
|
|
2649
|
+
) -> None:
|
|
2650
|
+
self._user_specified_trash = trash
|
|
2651
|
+
|
|
2652
|
+
@property
|
|
2653
|
+
@requires_version(2, 0)
|
|
2654
|
+
def name(self) -> str:
|
|
2655
|
+
"""
|
|
2656
|
+
The name string for the pipette.
|
|
2657
|
+
|
|
2658
|
+
From API version 2.15 to 2.22, this property returned an internal name for Flex
|
|
2659
|
+
pipettes. (e.g., ``"p1000_single_flex"``).
|
|
2660
|
+
|
|
2661
|
+
In API version 2.23 and later, this property returns the Python Protocol API
|
|
2662
|
+
:ref:`load name <new-pipette-models>` of Flex pipettes (e.g.,
|
|
2663
|
+
``"flex_1channel_1000"``).
|
|
2664
|
+
"""
|
|
2665
|
+
return self._core.get_pipette_name()
|
|
2666
|
+
|
|
2667
|
+
@property
|
|
2668
|
+
@requires_version(2, 0)
|
|
2669
|
+
def model(self) -> str:
|
|
2670
|
+
"""
|
|
2671
|
+
The model string for the pipette (e.g., ``'p300_single_v1.3'``)
|
|
2672
|
+
"""
|
|
2673
|
+
return self._core.get_model()
|
|
2674
|
+
|
|
2675
|
+
@property
|
|
2676
|
+
@requires_version(2, 0)
|
|
2677
|
+
def min_volume(self) -> float:
|
|
2678
|
+
"""
|
|
2679
|
+
The minimum volume, in µL, that the pipette can hold. This value may change
|
|
2680
|
+
based on the :ref:`volume mode <pipette-volume-modes>` that the pipette is
|
|
2681
|
+
currently configured for.
|
|
2682
|
+
"""
|
|
2683
|
+
return self._core.get_min_volume()
|
|
2684
|
+
|
|
2685
|
+
@property
|
|
2686
|
+
@requires_version(2, 0)
|
|
2687
|
+
def max_volume(self) -> float:
|
|
2688
|
+
"""
|
|
2689
|
+
The maximum volume, in µL, that the pipette can hold.
|
|
2690
|
+
|
|
2691
|
+
The maximum volume that you can actually aspirate might be lower than this,
|
|
2692
|
+
depending on what kind of tip is attached to this pipette. For example, a P300
|
|
2693
|
+
Single-Channel pipette always has a ``max_volume`` of 300 µL, but if it's using
|
|
2694
|
+
a 200 µL filter tip, its usable volume would be limited to 200 µL.
|
|
2695
|
+
"""
|
|
2696
|
+
return self._core.get_max_volume()
|
|
2697
|
+
|
|
2698
|
+
@property
|
|
2699
|
+
@requires_version(2, 0)
|
|
2700
|
+
def current_volume(self) -> float:
|
|
2701
|
+
"""
|
|
2702
|
+
The current amount of liquid held in the pipette, measured in µL.
|
|
2703
|
+
"""
|
|
2704
|
+
return self._core.get_current_volume()
|
|
2705
|
+
|
|
2706
|
+
@property
|
|
2707
|
+
@requires_version(2, 7)
|
|
2708
|
+
def has_tip(self) -> bool:
|
|
2709
|
+
"""Whether this instrument has a tip attached or not.
|
|
2710
|
+
|
|
2711
|
+
The value of this property is determined logically by the API, not by detecting
|
|
2712
|
+
the physical presence of a tip. This is the case even on Flex, which has sensors
|
|
2713
|
+
to detect tip attachment.
|
|
2714
|
+
"""
|
|
2715
|
+
return self._core.has_tip()
|
|
2716
|
+
|
|
2717
|
+
@property
|
|
2718
|
+
def _has_tip(self) -> bool:
|
|
2719
|
+
"""
|
|
2720
|
+
Internal function used to check whether this instrument has a
|
|
2721
|
+
tip attached or not.
|
|
2722
|
+
"""
|
|
2723
|
+
return self._core.has_tip()
|
|
2724
|
+
|
|
2725
|
+
@property
|
|
2726
|
+
@requires_version(2, 0)
|
|
2727
|
+
def hw_pipette(self) -> PipetteDict:
|
|
2728
|
+
"""View the information returned by the hardware API directly.
|
|
2729
|
+
|
|
2730
|
+
:raises: :py:class:`.types.PipetteNotAttachedError` if the pipette is
|
|
2731
|
+
no longer attached (should not happen).
|
|
2732
|
+
"""
|
|
2733
|
+
return self._core.get_hardware_state()
|
|
2734
|
+
|
|
2735
|
+
@property
|
|
2736
|
+
@requires_version(2, 0)
|
|
2737
|
+
def channels(self) -> int:
|
|
2738
|
+
"""The number of channels on the pipette.
|
|
2739
|
+
|
|
2740
|
+
Possible values are 1, 8, or 96.
|
|
2741
|
+
|
|
2742
|
+
See also :py:obj:`.type`.
|
|
2743
|
+
"""
|
|
2744
|
+
return self._core.get_channels()
|
|
2745
|
+
|
|
2746
|
+
@property
|
|
2747
|
+
@requires_version(2, 16)
|
|
2748
|
+
def active_channels(self) -> int:
|
|
2749
|
+
"""The number of channels the pipette will use to pick up tips.
|
|
2750
|
+
|
|
2751
|
+
By default, all channels on the pipette. Use :py:meth:`.configure_nozzle_layout`
|
|
2752
|
+
to set the pipette to use fewer channels.
|
|
2753
|
+
"""
|
|
2754
|
+
return self._core.get_active_channels()
|
|
2755
|
+
|
|
2756
|
+
@property
|
|
2757
|
+
@requires_version(2, 2)
|
|
2758
|
+
def return_height(self) -> float:
|
|
2759
|
+
"""The height to return a tip to its tip rack.
|
|
2760
|
+
|
|
2761
|
+
:returns: A scaling factor to apply to the tip length.
|
|
2762
|
+
During :py:meth:`.drop_tip`, this factor is multiplied by the tip
|
|
2763
|
+
length to get the distance from the top of the well to drop the tip.
|
|
2764
|
+
"""
|
|
2765
|
+
return self._core.get_return_height()
|
|
2766
|
+
|
|
2767
|
+
@property
|
|
2768
|
+
@requires_version(2, 0)
|
|
2769
|
+
def well_bottom_clearance(self) -> "Clearances":
|
|
2770
|
+
"""The distance above the bottom of a well to aspirate or dispense.
|
|
2771
|
+
|
|
2772
|
+
This is an object with attributes ``aspirate`` and ``dispense``, describing the
|
|
2773
|
+
default height of the corresponding operation. The default is 1.0 mm for both
|
|
2774
|
+
aspirate and dispense.
|
|
2775
|
+
|
|
2776
|
+
When :py:meth:`aspirate` or :py:meth:`dispense` is given a :py:class:`.Well`
|
|
2777
|
+
rather than a full :py:class:`.Location`, the robot will move this distance
|
|
2778
|
+
above the bottom of the well to aspirate or dispense.
|
|
2779
|
+
|
|
2780
|
+
To change, set the corresponding attribute::
|
|
2781
|
+
|
|
2782
|
+
pipette.well_bottom_clearance.aspirate = 2
|
|
2783
|
+
|
|
2784
|
+
"""
|
|
2785
|
+
return self._well_bottom_clearances
|
|
2786
|
+
|
|
2787
|
+
def _get_last_location_by_api_version(
|
|
2788
|
+
self,
|
|
2789
|
+
) -> Optional[Union[types.Location, TrashBin, WasteChute]]:
|
|
2790
|
+
"""Get the last location accessed by this pipette, if any.
|
|
2791
|
+
|
|
2792
|
+
In pre-engine Protocol API versions, this call omits the pipette mount.
|
|
2793
|
+
Between 2.14 (first engine PAPI version) and 2.23 this only returns None or a Location object.
|
|
2794
|
+
This is to preserve pre-existing, potentially buggy behavior.
|
|
2795
|
+
"""
|
|
2796
|
+
if self._api_version >= APIVersion(2, 24):
|
|
2797
|
+
return self._protocol_core.get_last_location(mount=self._core.get_mount())
|
|
2798
|
+
elif self._api_version >= ENGINE_CORE_API_VERSION:
|
|
2799
|
+
last_location = self._protocol_core.get_last_location(
|
|
2800
|
+
mount=self._core.get_mount()
|
|
2801
|
+
)
|
|
2802
|
+
return last_location if isinstance(last_location, types.Location) else None
|
|
2803
|
+
else:
|
|
2804
|
+
return self._protocol_core.get_last_location()
|
|
2805
|
+
|
|
2806
|
+
def __repr__(self) -> str:
|
|
2807
|
+
return "<{}: {} in {}>".format(
|
|
2808
|
+
self.__class__.__name__,
|
|
2809
|
+
self._core.get_model(),
|
|
2810
|
+
self._core.get_mount().name,
|
|
2811
|
+
)
|
|
2812
|
+
|
|
2813
|
+
def __str__(self) -> str:
|
|
2814
|
+
return "{} on {} mount".format(self._core.get_display_name(), self.mount)
|
|
2815
|
+
|
|
2816
|
+
@publisher.publish(command=cmds.configure_for_volume)
|
|
2817
|
+
@requires_version(2, 15)
|
|
2818
|
+
def configure_for_volume(self, volume: float) -> None:
|
|
2819
|
+
"""Configure a pipette to handle a specific volume of liquid, measured in µL.
|
|
2820
|
+
The pipette enters a volume mode depending on the volume provided. Changing
|
|
2821
|
+
pipette modes alters properties of the instance of
|
|
2822
|
+
:py:class:`.InstrumentContext`, such as default flow rate, minimum volume, and
|
|
2823
|
+
maximum volume. The pipette remains in the mode set by this function until it is
|
|
2824
|
+
called again.
|
|
2825
|
+
|
|
2826
|
+
The Flex 1-Channel 50 µL and Flex 8-Channel 50 µL pipettes must operate in a
|
|
2827
|
+
low-volume mode to accurately dispense very small volumes of liquid. Low-volume
|
|
2828
|
+
mode can only be set by calling ``configure_for_volume()``. See
|
|
2829
|
+
:ref:`pipette-volume-modes`.
|
|
2830
|
+
|
|
2831
|
+
.. note ::
|
|
2832
|
+
|
|
2833
|
+
Changing a pipette's mode will reset its :ref:`flow rates
|
|
2834
|
+
<new-plunger-flow-rates>`.
|
|
2835
|
+
|
|
2836
|
+
This function will raise an error if called when the pipette's tip contains
|
|
2837
|
+
liquid. It won't raise an error if a tip is not attached, but changing modes may
|
|
2838
|
+
affect which tips the pipette can subsequently pick up without raising an error.
|
|
2839
|
+
|
|
2840
|
+
This function will also raise an error if ``volume`` is outside of the
|
|
2841
|
+
:ref:`minimum and maximum capacities <new-pipette-models>` of the pipette (e.g.,
|
|
2842
|
+
setting ``volume=1`` for a Flex 1000 µL pipette).
|
|
2843
|
+
|
|
2844
|
+
:param volume: The volume, in µL, that the pipette will prepare to handle.
|
|
2845
|
+
:type volume: float
|
|
2846
|
+
"""
|
|
2847
|
+
if self._core.get_current_volume():
|
|
2848
|
+
raise CommandPreconditionViolated(
|
|
2849
|
+
message=f"Cannot switch modes of {str(self)} while it contains liquid"
|
|
2850
|
+
)
|
|
2851
|
+
if volume < 0:
|
|
2852
|
+
raise CommandParameterLimitViolated(
|
|
2853
|
+
command_name="configure_for_volume",
|
|
2854
|
+
parameter_name="volume",
|
|
2855
|
+
limit_statement="must be greater than 0",
|
|
2856
|
+
actual_value=str(volume),
|
|
2857
|
+
)
|
|
2858
|
+
last_location = self._get_last_location_by_api_version()
|
|
2859
|
+
if (
|
|
2860
|
+
last_location
|
|
2861
|
+
and isinstance(last_location, types.Location)
|
|
2862
|
+
and isinstance(last_location.labware, labware.Well)
|
|
2863
|
+
):
|
|
2864
|
+
self.move_to(last_location.labware.top())
|
|
2865
|
+
self._core.configure_for_volume(volume)
|
|
2866
|
+
|
|
2867
|
+
@requires_version(2, 16)
|
|
2868
|
+
def prepare_to_aspirate(self) -> None:
|
|
2869
|
+
"""Prepare a pipette for aspiration.
|
|
2870
|
+
|
|
2871
|
+
Before a pipette can aspirate into an empty tip, the plunger must be in its
|
|
2872
|
+
bottom position. After dropping a tip or blowing out, the plunger will be in a
|
|
2873
|
+
different position. This function moves the plunger to the bottom position,
|
|
2874
|
+
regardless of its current position, to make sure that the pipette is ready to
|
|
2875
|
+
aspirate.
|
|
2876
|
+
|
|
2877
|
+
You rarely need to call this function. The API automatically prepares the
|
|
2878
|
+
pipette for aspiration as part of other commands:
|
|
2879
|
+
|
|
2880
|
+
- After picking up a tip with :py:meth:`.pick_up_tip`.
|
|
2881
|
+
- When calling :py:meth:`.aspirate`, if the pipette isn't already prepared.
|
|
2882
|
+
If the pipette is in a well, it will move out of the well, move the plunger,
|
|
2883
|
+
and then move back.
|
|
2884
|
+
|
|
2885
|
+
Use ``prepare_to_aspirate()`` when you need to control exactly when the plunger
|
|
2886
|
+
motion will happen. A common use case is a pre-wetting routine, which requires
|
|
2887
|
+
preparing for aspiration, moving into a well, and then aspirating *without
|
|
2888
|
+
leaving the well*::
|
|
2889
|
+
|
|
2890
|
+
pipette.move_to(well.bottom(z=2))
|
|
2891
|
+
protocol.delay(5)
|
|
2892
|
+
pipette.mix(10, 10)
|
|
2893
|
+
pipette.move_to(well.top(z=5))
|
|
2894
|
+
pipette.blow_out()
|
|
2895
|
+
pipette.prepare_to_aspirate()
|
|
2896
|
+
pipette.move_to(well.bottom(z=2))
|
|
2897
|
+
protocol.delay(5)
|
|
2898
|
+
pipette.aspirate(10, well.bottom(z=2))
|
|
2899
|
+
|
|
2900
|
+
The call to ``prepare_to_aspirate()`` means that the plunger will be in the
|
|
2901
|
+
bottom position before the call to ``aspirate()``. Since it doesn't need to
|
|
2902
|
+
prepare again, it will not move up out of the well to move the plunger. It will
|
|
2903
|
+
aspirate in place.
|
|
2904
|
+
"""
|
|
2905
|
+
if self._core.get_current_volume():
|
|
2906
|
+
raise CommandPreconditionViolated(
|
|
2907
|
+
message=f"Cannot prepare {str(self)} for aspirate while it contains liquid."
|
|
2908
|
+
)
|
|
2909
|
+
self._core.prepare_to_aspirate()
|
|
2910
|
+
|
|
2911
|
+
@publisher.publish(command=cmds.configure_nozzle_layout)
|
|
2912
|
+
@requires_version(2, 16)
|
|
2913
|
+
def configure_nozzle_layout(
|
|
2914
|
+
self,
|
|
2915
|
+
style: NozzleLayout,
|
|
2916
|
+
start: Optional[str] = None,
|
|
2917
|
+
end: Optional[str] = None,
|
|
2918
|
+
front_right: Optional[str] = None,
|
|
2919
|
+
back_left: Optional[str] = None,
|
|
2920
|
+
tip_racks: Optional[List[labware.Labware]] = None,
|
|
2921
|
+
) -> None:
|
|
2922
|
+
"""Configure how many tips the 8-channel or 96-channel pipette will pick up.
|
|
2923
|
+
|
|
2924
|
+
Changing the nozzle layout will affect gantry movement for all subsequent
|
|
2925
|
+
pipetting actions that the pipette performs. It also alters the pipette's
|
|
2926
|
+
behavior for picking up tips. The pipette will continue to use the specified
|
|
2927
|
+
layout until this function is called again.
|
|
2928
|
+
|
|
2929
|
+
.. note::
|
|
2930
|
+
When picking up fewer than 96 tips at once, the tip rack *must not* be
|
|
2931
|
+
placed in a tip rack adapter in the deck. If you try to pick up fewer than 96
|
|
2932
|
+
tips from a tip rack that is in an adapter, the API will raise an error.
|
|
2933
|
+
|
|
2934
|
+
:param style: The shape of the nozzle layout.
|
|
2935
|
+
You must :ref:`import the layout constant <nozzle-layouts>` in order to use it.
|
|
2936
|
+
|
|
2937
|
+
- ``ALL`` resets the pipette to use all of its nozzles. Calling
|
|
2938
|
+
``configure_nozzle_layout`` with no arguments also resets the pipette.
|
|
2939
|
+
- ``COLUMN`` sets a 96-channel pipette to use 8 nozzles, aligned from front to back
|
|
2940
|
+
with respect to the deck. This corresponds to a column of wells on labware.
|
|
2941
|
+
For 8-channel pipettes, use ``ALL`` instead.
|
|
2942
|
+
- ``PARTIAL_COLUMN`` sets an 8-channel pipette to use 2--7 nozzles, aligned from front to back
|
|
2943
|
+
with respect to the deck. Not compatible with the 96-channel pipette.
|
|
2944
|
+
- ``ROW`` sets a 96-channel pipette to use 12 nozzles, aligned from left to right
|
|
2945
|
+
with respect to the deck. This corresponds to a row of wells on labware.
|
|
2946
|
+
Not compatible with 8-channel pipettes.
|
|
2947
|
+
- ``SINGLE`` sets the pipette to use 1 nozzle. This corresponds to a single well on labware.
|
|
2948
|
+
|
|
2949
|
+
:type style: ``NozzleLayout`` or ``None``
|
|
2950
|
+
:param start: The primary nozzle of the layout, which the robot uses
|
|
2951
|
+
to determine how it will move to different locations on the deck. The string
|
|
2952
|
+
should be of the same format used when identifying wells by name.
|
|
2953
|
+
Required unless setting ``style=ALL``.
|
|
2954
|
+
|
|
2955
|
+
.. note::
|
|
2956
|
+
If possible, don't use both ``start="A1"`` and ``start="A12"`` to pick up
|
|
2957
|
+
tips *from the same rack*. Doing so can affect positional accuracy.
|
|
2958
|
+
|
|
2959
|
+
:type start: str or ``None``
|
|
2960
|
+
:param end: The nozzle at the end of a linear layout, which is used
|
|
2961
|
+
to determine how many tips will be picked up by a pipette. The string
|
|
2962
|
+
should be of the same format used when identifying wells by name.
|
|
2963
|
+
Required when setting ``style=PARTIAL_COLUMN``.
|
|
2964
|
+
|
|
2965
|
+
:type end: str or ``None``
|
|
2966
|
+
:param tip_racks: Behaves the same as setting the ``tip_racks`` parameter of
|
|
2967
|
+
:py:meth:`.load_instrument`. If not specified, the new configuration resets
|
|
2968
|
+
:py:obj:`.InstrumentContext.tip_racks` and you must specify the location
|
|
2969
|
+
every time you call :py:meth:`~.InstrumentContext.pick_up_tip`.
|
|
2970
|
+
:type tip_racks: List[:py:class:`.Labware`]
|
|
2971
|
+
|
|
2972
|
+
.. versionchanged:: 2.20
|
|
2973
|
+
Added partial column, row, and single layouts.
|
|
2974
|
+
"""
|
|
2975
|
+
# TODO: add the following back into the docstring when QUADRANT is supported
|
|
2976
|
+
#
|
|
2977
|
+
# :param front_right: The nozzle at the front left of the layout. Only used for
|
|
2978
|
+
# NozzleLayout.QUADRANT configurations.
|
|
2979
|
+
# :type front_right: str or ``None``
|
|
2980
|
+
#
|
|
2981
|
+
# NOTE: Disabled layouts error case can be removed once desired map configurations
|
|
2982
|
+
# have appropriate data regarding tip-type to map current values added to the
|
|
2983
|
+
# pipette definitions.
|
|
2984
|
+
|
|
2985
|
+
disabled_layouts = [
|
|
2986
|
+
NozzleLayout.QUADRANT,
|
|
2987
|
+
]
|
|
2988
|
+
if style in disabled_layouts:
|
|
2989
|
+
# todo(mm, 2024-08-20): UnsupportedAPIError boils down to an API_REMOVED
|
|
2990
|
+
# error code, which is not correct here.
|
|
2991
|
+
raise UnsupportedAPIError(
|
|
2992
|
+
message=f"Nozzle layout configuration of style {style.value} is currently unsupported."
|
|
2993
|
+
)
|
|
2994
|
+
|
|
2995
|
+
original_enabled_layouts = [NozzleLayout.COLUMN, NozzleLayout.ALL]
|
|
2996
|
+
if (
|
|
2997
|
+
self._api_version
|
|
2998
|
+
< _PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN
|
|
2999
|
+
) and (style not in original_enabled_layouts):
|
|
3000
|
+
raise APIVersionError(
|
|
3001
|
+
api_element=f"Nozzle layout configuration of style {style.value}",
|
|
3002
|
+
until_version=f"{_PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN}",
|
|
3003
|
+
current_version=f"{self._api_version}",
|
|
3004
|
+
)
|
|
3005
|
+
|
|
3006
|
+
front_right_resolved = front_right
|
|
3007
|
+
back_left_resolved = back_left
|
|
3008
|
+
validated_start: Optional[str] = None
|
|
3009
|
+
match style:
|
|
3010
|
+
case NozzleLayout.SINGLE:
|
|
3011
|
+
validated_start = _check_valid_start_nozzle(style, start)
|
|
3012
|
+
_raise_if_has_end_or_front_right_or_back_left(
|
|
3013
|
+
style, end, front_right, back_left
|
|
3014
|
+
)
|
|
3015
|
+
case NozzleLayout.COLUMN | NozzleLayout.ROW:
|
|
3016
|
+
self._raise_if_configuration_not_supported_by_pipette(style)
|
|
3017
|
+
validated_start = _check_valid_start_nozzle(style, start)
|
|
3018
|
+
_raise_if_has_end_or_front_right_or_back_left(
|
|
3019
|
+
style, end, front_right, back_left
|
|
3020
|
+
)
|
|
3021
|
+
case NozzleLayout.PARTIAL_COLUMN:
|
|
3022
|
+
self._raise_if_configuration_not_supported_by_pipette(style)
|
|
3023
|
+
validated_start = _check_valid_start_nozzle(style, start)
|
|
3024
|
+
validated_end = _check_valid_end_nozzle(validated_start, end)
|
|
3025
|
+
_raise_if_has_front_right_or_back_left_for_partial_column(
|
|
3026
|
+
front_right, back_left
|
|
3027
|
+
)
|
|
3028
|
+
# Convert 'validated_end' to front_right or back_left as appropriate
|
|
3029
|
+
if validated_start == "H1" or validated_start == "H12":
|
|
3030
|
+
back_left_resolved = validated_end
|
|
3031
|
+
front_right_resolved = validated_start
|
|
3032
|
+
elif start == "A1" or start == "A12":
|
|
3033
|
+
front_right_resolved = validated_end
|
|
3034
|
+
back_left_resolved = validated_start
|
|
3035
|
+
case NozzleLayout.QUADRANT:
|
|
3036
|
+
validated_start = _check_valid_start_nozzle(style, start)
|
|
3037
|
+
_raise_if_has_end_nozzle_for_quadrant(end)
|
|
3038
|
+
_raise_if_no_front_right_or_back_left_for_quadrant(
|
|
3039
|
+
front_right, back_left
|
|
3040
|
+
)
|
|
3041
|
+
if front_right is None:
|
|
3042
|
+
front_right_resolved = validated_start
|
|
3043
|
+
elif back_left is None:
|
|
3044
|
+
back_left_resolved = validated_start
|
|
3045
|
+
case NozzleLayout.ALL:
|
|
3046
|
+
validated_start = start
|
|
3047
|
+
if any([start, end, front_right, back_left]):
|
|
3048
|
+
_log.warning(
|
|
3049
|
+
"Parameters 'start', 'end', 'front_right', 'back_left' specified"
|
|
3050
|
+
" for ALL nozzle configuration will be ignored."
|
|
3051
|
+
)
|
|
3052
|
+
|
|
3053
|
+
self._core.configure_nozzle_layout(
|
|
3054
|
+
style,
|
|
3055
|
+
primary_nozzle=validated_start,
|
|
3056
|
+
front_right_nozzle=front_right_resolved,
|
|
3057
|
+
back_left_nozzle=back_left_resolved,
|
|
3058
|
+
)
|
|
3059
|
+
self._tip_racks = tip_racks or []
|
|
3060
|
+
|
|
3061
|
+
@requires_version(2, 20)
|
|
3062
|
+
def detect_liquid_presence(self, well: labware.Well) -> bool:
|
|
3063
|
+
"""Checks for liquid in a well.
|
|
3064
|
+
|
|
3065
|
+
Returns ``True`` if liquid is present and ``False`` if liquid is not present. Will not raise an error if it does not detect liquid. When simulating a protocol, the check always succeeds (returns ``True``). Works with Flex 1-, 8-, and 96-channel pipettes. See :ref:`detect-liquid-presence`.
|
|
3066
|
+
|
|
3067
|
+
.. note::
|
|
3068
|
+
The pressure sensors for the Flex 8-channel pipette are on channels 1 and 8 (positions A1 and H1). For the Flex 96-channel pipette, the pressure sensors are on channels 1 and 96 (positions A1 and H12). Other channels on multi-channel pipettes do not have sensors and cannot detect liquid.
|
|
3069
|
+
"""
|
|
3070
|
+
self._raise_if_pressure_not_supported_by_pipette()
|
|
3071
|
+
loc = well.top()
|
|
3072
|
+
return self._core.detect_liquid_presence(well._core, loc)
|
|
3073
|
+
|
|
3074
|
+
@requires_version(2, 20)
|
|
3075
|
+
def require_liquid_presence(self, well: labware.Well) -> None:
|
|
3076
|
+
"""Check for liquid in a well and raises an error if none is detected.
|
|
3077
|
+
|
|
3078
|
+
When this method raises an error, Flex will offer the opportunity to enter recovery mode. In recovery mode, you can manually add liquid to resolve the error. When simulating a protocol, the check always succeeds (does not raise an error). Works with Flex 1-, 8-, and 96-channel pipettes. See :ref:`lpd` and :ref:`require-liquid-presence`.
|
|
3079
|
+
|
|
3080
|
+
.. note::
|
|
3081
|
+
The pressure sensors for the Flex 8-channel pipette are on channels 1 and 8 (positions A1 and H1). For the Flex 96-channel pipette, the pressure sensors are on channels 1 and 96 (positions A1 and H12). Other channels on multi-channel pipettes do not have sensors and cannot detect liquid.
|
|
3082
|
+
"""
|
|
3083
|
+
self._raise_if_pressure_not_supported_by_pipette()
|
|
3084
|
+
loc = well.top()
|
|
3085
|
+
self._core.liquid_probe_with_recovery(well._core, loc)
|
|
3086
|
+
|
|
3087
|
+
@requires_version(2, 20)
|
|
3088
|
+
def measure_liquid_height(self, well: labware.Well) -> LiquidTrackingType:
|
|
3089
|
+
"""Check the height of the liquid within a well.
|
|
3090
|
+
|
|
3091
|
+
:returns: The height, in mm, of the liquid from the bottom of the well.
|
|
3092
|
+
"""
|
|
3093
|
+
self._raise_if_pressure_not_supported_by_pipette()
|
|
3094
|
+
loc = well.top()
|
|
3095
|
+
self._core.liquid_probe_with_recovery(well._core, loc)
|
|
3096
|
+
return well.current_liquid_height()
|
|
3097
|
+
|
|
3098
|
+
def _raise_if_configuration_not_supported_by_pipette(
|
|
3099
|
+
self, style: NozzleLayout
|
|
3100
|
+
) -> None:
|
|
3101
|
+
match style:
|
|
3102
|
+
case NozzleLayout.COLUMN | NozzleLayout.ROW:
|
|
3103
|
+
if self.channels != 96:
|
|
3104
|
+
raise ValueError(
|
|
3105
|
+
f"{style.value} configuration is only supported on 96-Channel pipettes."
|
|
3106
|
+
)
|
|
3107
|
+
case NozzleLayout.PARTIAL_COLUMN:
|
|
3108
|
+
if self.channels != 8:
|
|
3109
|
+
raise ValueError(
|
|
3110
|
+
"Partial column configuration is only supported on 8-Channel pipettes."
|
|
3111
|
+
)
|
|
3112
|
+
# SINGLE, QUADRANT and ALL are supported by all pipettes
|
|
3113
|
+
|
|
3114
|
+
def _raise_if_pressure_not_supported_by_pipette(self) -> None:
|
|
3115
|
+
if not self._core._pressure_supported_by_pipette():
|
|
3116
|
+
raise UnsupportedHardwareCommand(
|
|
3117
|
+
"Pressure sensor not available for this pipette"
|
|
3118
|
+
)
|
|
3119
|
+
|
|
3120
|
+
def _handle_aspirate_target(
|
|
3121
|
+
self, target: Union[validation.WellTarget, validation.PointTarget]
|
|
3122
|
+
) -> tuple[
|
|
3123
|
+
types.Location, Optional[labware.Well], Optional[types.MeniscusTrackingTarget]
|
|
3124
|
+
]:
|
|
3125
|
+
if isinstance(target, validation.WellTarget):
|
|
3126
|
+
if target.location:
|
|
3127
|
+
return target.location, target.well, target.location.meniscus_tracking
|
|
3128
|
+
|
|
3129
|
+
else:
|
|
3130
|
+
return (
|
|
3131
|
+
target.well.bottom(z=self._well_bottom_clearances.aspirate),
|
|
3132
|
+
target.well,
|
|
3133
|
+
None,
|
|
3134
|
+
)
|
|
3135
|
+
if isinstance(target, validation.PointTarget):
|
|
3136
|
+
return target.location, None, None
|
|
3137
|
+
|
|
3138
|
+
def _handle_dispense_target(
|
|
3139
|
+
self, target: Union[validation.WellTarget, validation.PointTarget]
|
|
3140
|
+
) -> tuple[
|
|
3141
|
+
types.Location, Optional[labware.Well], Optional[types.MeniscusTrackingTarget]
|
|
3142
|
+
]:
|
|
3143
|
+
if isinstance(target, validation.WellTarget):
|
|
3144
|
+
if target.location:
|
|
3145
|
+
return target.location, target.well, target.location.meniscus_tracking
|
|
3146
|
+
elif target.well.parent._core.is_fixed_trash():
|
|
3147
|
+
return target.well.top(), target.well, None
|
|
3148
|
+
else:
|
|
3149
|
+
return (
|
|
3150
|
+
target.well.bottom(z=self._well_bottom_clearances.dispense),
|
|
3151
|
+
target.well,
|
|
3152
|
+
None,
|
|
3153
|
+
)
|
|
3154
|
+
if isinstance(target, validation.PointTarget):
|
|
3155
|
+
return target.location, None, None
|
|
3156
|
+
|
|
3157
|
+
def _get_current_tip_source_well(self) -> Optional[labware.Well]:
|
|
3158
|
+
tip_rack_cores = self._core.get_tip_origin()
|
|
3159
|
+
if tip_rack_cores is None:
|
|
3160
|
+
return None
|
|
3161
|
+
labware_core, well_core = tip_rack_cores
|
|
3162
|
+
tip_rack_labware = self._core_map.get(labware_core)
|
|
3163
|
+
return labware.Well(
|
|
3164
|
+
parent=tip_rack_labware, core=well_core, api_version=self._api_version
|
|
3165
|
+
)
|
|
3166
|
+
|
|
3167
|
+
@property
|
|
3168
|
+
def _last_tip_picked_up_from(self) -> Optional[labware.Well]:
|
|
3169
|
+
"""
|
|
3170
|
+
.. deprecated:: 2.25
|
|
3171
|
+
Use :py:obj:`ProtocolContext.current_tip_source_well` instead.
|
|
3172
|
+
|
|
3173
|
+
If the pipette has a tip on it, returns the tip rack well it was picked up from.
|
|
3174
|
+
Otherwise will return ``None``.
|
|
3175
|
+
"""
|
|
3176
|
+
return self._get_current_tip_source_well()
|
|
3177
|
+
|
|
3178
|
+
@requires_version(2, 25)
|
|
3179
|
+
def current_tip_source_well(self) -> Optional[labware.Well]:
|
|
3180
|
+
"""Returns the tip rack well the current tip has been picked up from.
|
|
3181
|
+
|
|
3182
|
+
If there is no tip currently on the pipette, this will return ``None``.
|
|
3183
|
+
"""
|
|
3184
|
+
return self._get_current_tip_source_well()
|
|
3185
|
+
|
|
3186
|
+
|
|
3187
|
+
class AutoProbeDisable:
|
|
3188
|
+
"""Use this class to temporarily disable automatic liquid presence detection."""
|
|
3189
|
+
|
|
3190
|
+
def __init__(self, instrument: InstrumentContext):
|
|
3191
|
+
self.instrument = instrument
|
|
3192
|
+
|
|
3193
|
+
def __enter__(self) -> None:
|
|
3194
|
+
if self.instrument.api_version >= APIVersion(2, 21):
|
|
3195
|
+
self.auto_presence = self.instrument.liquid_presence_detection
|
|
3196
|
+
self.instrument.liquid_presence_detection = False
|
|
3197
|
+
|
|
3198
|
+
def __exit__(self, *args: Any, **kwargs: Any) -> None:
|
|
3199
|
+
if self.instrument.api_version >= APIVersion(2, 21):
|
|
3200
|
+
self.instrument.liquid_presence_detection = self.auto_presence
|
|
3201
|
+
|
|
3202
|
+
|
|
3203
|
+
def _raise_if_has_end_or_front_right_or_back_left(
|
|
3204
|
+
style: NozzleLayout,
|
|
3205
|
+
end: Optional[str],
|
|
3206
|
+
front_right: Optional[str],
|
|
3207
|
+
back_left: Optional[str],
|
|
3208
|
+
) -> None:
|
|
3209
|
+
if any([end, front_right, back_left]):
|
|
3210
|
+
raise ValueError(
|
|
3211
|
+
f"Parameters 'end', 'front_right' and 'back_left' cannot be used with "
|
|
3212
|
+
f"the {style.name} nozzle configuration."
|
|
3213
|
+
)
|
|
3214
|
+
|
|
3215
|
+
|
|
3216
|
+
def _check_valid_start_nozzle(style: NozzleLayout, start: Optional[str]) -> str:
|
|
3217
|
+
if start is None:
|
|
3218
|
+
raise ValueError(
|
|
3219
|
+
f"Cannot configure a nozzle layout of style {style.value} without a starting nozzle."
|
|
3220
|
+
)
|
|
3221
|
+
if start not in types.ALLOWED_PRIMARY_NOZZLES:
|
|
3222
|
+
raise ValueError(
|
|
3223
|
+
f"Starting nozzle specified is not one of {types.ALLOWED_PRIMARY_NOZZLES}."
|
|
3224
|
+
)
|
|
3225
|
+
return start
|
|
3226
|
+
|
|
3227
|
+
|
|
3228
|
+
def _check_valid_end_nozzle(start: str, end: Optional[str]) -> str:
|
|
3229
|
+
if end is None:
|
|
3230
|
+
raise ValueError("Partial column configurations require the 'end' parameter.")
|
|
3231
|
+
if start[0] in end:
|
|
3232
|
+
raise ValueError(
|
|
3233
|
+
"The 'start' and 'end' parameters of a partial column configuration cannot be in the same row."
|
|
3234
|
+
)
|
|
3235
|
+
if start == "H1" or start == "H12":
|
|
3236
|
+
if "A" in end:
|
|
3237
|
+
raise ValueError(
|
|
3238
|
+
f"A partial column configuration with 'start'={start} cannot have its 'end' parameter be in row A. Use `ALL` configuration to utilize all nozzles."
|
|
3239
|
+
)
|
|
3240
|
+
elif start == "A1" or start == "A12":
|
|
3241
|
+
if "H" in end:
|
|
3242
|
+
raise ValueError(
|
|
3243
|
+
f"A partial column configuration with 'start'={start} cannot have its 'end' parameter be in row H. Use `ALL` configuration to utilize all nozzles."
|
|
3244
|
+
)
|
|
3245
|
+
return end
|
|
3246
|
+
|
|
3247
|
+
|
|
3248
|
+
def _raise_if_no_front_right_or_back_left_for_quadrant(
|
|
3249
|
+
front_right: Optional[str], back_left: Optional[str]
|
|
3250
|
+
) -> None:
|
|
3251
|
+
if front_right is None and back_left is None:
|
|
3252
|
+
raise ValueError(
|
|
3253
|
+
"Cannot configure a QUADRANT layout without a front right or back left nozzle."
|
|
3254
|
+
)
|
|
3255
|
+
|
|
3256
|
+
|
|
3257
|
+
def _raise_if_has_end_nozzle_for_quadrant(end: Optional[str]) -> None:
|
|
3258
|
+
if end is not None:
|
|
3259
|
+
raise ValueError(
|
|
3260
|
+
"Parameter 'end' is not supported for QUADRANT configuration."
|
|
3261
|
+
" Use 'front_right' and 'back_left' arguments to specify the quadrant nozzle map instead."
|
|
3262
|
+
)
|
|
3263
|
+
|
|
3264
|
+
|
|
3265
|
+
def _raise_if_has_front_right_or_back_left_for_partial_column(
|
|
3266
|
+
front_right: Optional[str], back_left: Optional[str]
|
|
3267
|
+
) -> None:
|
|
3268
|
+
if any([front_right, back_left]):
|
|
3269
|
+
raise ValueError(
|
|
3270
|
+
"Parameters 'front_right' and 'back_left' cannot be used with "
|
|
3271
|
+
"the PARTIAL_COLUMN configuration."
|
|
3272
|
+
)
|