diff --git a/api/src/opentrons/protocols/__init__.py b/api/src/opentrons/protocols/__init__.py index 7bfd2da924d..1b594411e2a 100644 --- a/api/src/opentrons/protocols/__init__.py +++ b/api/src/opentrons/protocols/__init__.py @@ -77,6 +77,15 @@ def _get_location(loaded_labware, command_type, params, default_values): if offset_from_bottom is None: # not all commands use offsets + + # touch-tip uses offset from top, not bottom, as default + # when offsetFromBottomMm command-specific value is unset + if command_type == 'touch-tip': + # TODO: Ian 2018-10-29 remove this `-1` when + # touch-tip-mm-from-top is a required field + return labware.wells(well).top( + z=default_values.get('touch-tip-mm-from-top', -1)) + return labware.wells(well) return labware.wells(well).bottom(offset_from_bottom) @@ -178,7 +187,18 @@ def dispatch_commands(protocol_data, loaded_pipettes, loaded_labware): # noqa: pipette.dispense(volume, location) elif command_type == 'touch-tip': - pipette.touch_tip(location) + # NOTE: if touch_tip can take a location tuple, + # this can be much simpler + (well_object, loc_tuple) = location + + # Use the offset baked into the well_object. + # Do not allow API to apply its v_offset kwarg default value, + # and do not apply the JSON protocol's default offset. + z_from_bottom = loc_tuple[2] + offset_from_top = ( + well_object.properties['depth'] - z_from_bottom) * -1 + + pipette.touch_tip(well_object, v_offset=offset_from_top) def execute_protocol(protocol): diff --git a/protocol-designer/src/constants.js b/protocol-designer/src/constants.js index 206bd99a3b9..233d1aef22a 100644 --- a/protocol-designer/src/constants.js +++ b/protocol-designer/src/constants.js @@ -51,6 +51,10 @@ export const END_TERMINAL_TITLE = 'FINAL DECK STATE' export const DEFAULT_CHANGE_TIP_OPTION: 'always' = 'always' export const DEFAULT_MM_FROM_BOTTOM_ASPIRATE = 1 export const DEFAULT_MM_FROM_BOTTOM_DISPENSE = 0.5 + +// NOTE: in the negative Z direction, to go down from top +export const DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP = -1 + export const DEFAULT_WELL_ORDER_FIRST_OPTION: 't2b' = 't2b' export const DEFAULT_WELL_ORDER_SECOND_OPTION: 'l2r' = 'l2r' diff --git a/protocol-designer/src/file-data/selectors/fileCreator.js b/protocol-designer/src/file-data/selectors/fileCreator.js index 140db626faa..8d3d5a53419 100644 --- a/protocol-designer/src/file-data/selectors/fileCreator.js +++ b/protocol-designer/src/file-data/selectors/fileCreator.js @@ -10,6 +10,7 @@ import {selectors as steplistSelectors} from '../../steplist' import { DEFAULT_MM_FROM_BOTTOM_ASPIRATE, DEFAULT_MM_FROM_BOTTOM_DISPENSE, + DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP, } from '../../constants' import type {BaseState} from '../../types' import type {ProtocolFile, FilePipette, FileLabware} from '../../file-types' @@ -29,6 +30,7 @@ const executionDefaults = { 'dispense-flow-rate': getPropertyAllPipettes('dispenseFlowRate'), 'aspirate-mm-from-bottom': DEFAULT_MM_FROM_BOTTOM_ASPIRATE, 'dispense-mm-from-bottom': DEFAULT_MM_FROM_BOTTOM_DISPENSE, + 'touch-tip-mm-from-top': DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP, } export const createFile: BaseState => ProtocolFile = createSelector( diff --git a/protocol-designer/src/step-generation/test-with-flow/touchTip.test.js b/protocol-designer/src/step-generation/test-with-flow/touchTip.test.js index 3395c2a2b75..c66fb2dc483 100644 --- a/protocol-designer/src/step-generation/test-with-flow/touchTip.test.js +++ b/protocol-designer/src/step-generation/test-with-flow/touchTip.test.js @@ -39,6 +39,27 @@ describe('touchTip', () => { expect(result.robotState).toEqual(robotStateWithTip) }) + test('touchTip with tip, specifying offsetFromBottomMm', () => { + const result = touchTip({ + pipette: 'p300SingleId', + labware: 'sourcePlateId', + well: 'A1', + offsetFromBottomMm: 10, + })(robotStateWithTip) + + expect(result.commands).toEqual([{ + command: 'touch-tip', + params: { + pipette: 'p300SingleId', + labware: 'sourcePlateId', + well: 'A1', + offsetFromBottomMm: 10, + }, + }]) + + expect(result.robotState).toEqual(robotStateWithTip) + }) + test('touchTip with invalid pipette ID should throw error', () => { const result = touchTipWithErrors({ pipette: 'badPipette', diff --git a/protocol-designer/src/step-generation/touchTip.js b/protocol-designer/src/step-generation/touchTip.js index 212ab26839e..2b7131e99a0 100644 --- a/protocol-designer/src/step-generation/touchTip.js +++ b/protocol-designer/src/step-generation/touchTip.js @@ -1,12 +1,12 @@ // @flow // import cloneDeep from 'lodash/cloneDeep' import {noTipOnPipette, pipetteDoesNotExist} from './errorCreators' -import type {RobotState, CommandCreator, CommandCreatorError, PipetteLabwareFields} from './' +import type {RobotState, CommandCreator, CommandCreatorError, TouchTipArgs} from './' -const touchTip = (args: PipetteLabwareFields): CommandCreator => (prevRobotState: RobotState) => { +const touchTip = (args: TouchTipArgs): CommandCreator => (prevRobotState: RobotState) => { /** touchTip with given args. Requires tip. */ const actionName = 'touchTip' - const {pipette, labware, well} = args + const {pipette, labware, well, offsetFromBottomMm} = args const pipetteData = prevRobotState.instruments[pipette] @@ -30,6 +30,9 @@ const touchTip = (args: PipetteLabwareFields): CommandCreator => (prevRobotState pipette, labware, well, + offsetFromBottomMm: offsetFromBottomMm == null + ? undefined + : offsetFromBottomMm, }, }] diff --git a/protocol-designer/src/step-generation/types.js b/protocol-designer/src/step-generation/types.js index a3f42dd5c24..1b6adff996f 100644 --- a/protocol-designer/src/step-generation/types.js +++ b/protocol-designer/src/step-generation/types.js @@ -207,6 +207,11 @@ export type PipetteLabwareFields = {| /* TODO optional uL/sec (or uL/minute???) speed here */ |} +export type TouchTipArgs = {| + ...PipetteLabwareFields, + offsetFromBottomMm?: ?number, +|} + export type AspirateDispenseArgs = {| ...PipetteLabwareFields, volume: number, @@ -217,10 +222,10 @@ export type Command = {| command: 'aspirate' | 'dispense', params: AspirateDispenseArgs, |} | {| - command: 'pick-up-tip' | 'drop-tip' | 'touch-tip', + command: 'pick-up-tip' | 'drop-tip' | 'blowout', params: PipetteLabwareFields, |} | {| - command: 'blowout', + command: 'touch-tip', params: {| ...PipetteLabwareFields, offsetFromBottomMm?: ?number, diff --git a/shared-data/protocol-json-schema/protocol-schema.json b/shared-data/protocol-json-schema/protocol-schema.json index 9f90b7ba9d6..595c8e96555 100644 --- a/shared-data/protocol-json-schema/protocol-schema.json +++ b/shared-data/protocol-json-schema/protocol-schema.json @@ -143,6 +143,7 @@ "default-values": { "description": "Default values required for protocol execution", "type": "object", + "$note": "TODO: Ian 2018-10-29 make touch-tip-mm-from-top required (breaking change)", "required": [ "aspirate-flow-rate", "dispense-flow-rate", @@ -153,7 +154,8 @@ "aspirate-flow-rate": {"$ref": "#/definitions/flow-rate-for-pipettes"}, "dispense-flow-rate": {"$ref": "#/definitions/flow-rate-for-pipettes"}, "aspirate-mm-from-bottom": {"$ref": "#/definitions/mm-offset"}, - "dispense-mm-from-bottom": {"$ref": "#/definitions/mm-offset"} + "dispense-mm-from-bottom": {"$ref": "#/definitions/mm-offset"}, + "touch-tip-mm-from-top": {"$ref": "#/definitions/mm-offset"} } }, @@ -282,13 +284,32 @@ }, { - "description": "Pick up tip / drop tip / touch tip / blowout commands", + "description": "Touch tip commands", "type": "object", "required": ["command", "params"], "additionalProperties": false, "properties": { "command": { - "enum": ["pick-up-tip", "drop-tip", "touch-tip", "blowout"] + "enum": ["touch-tip"] + }, + "params": { + "allOf": [ + {"$ref": "#/definitions/pipette-access-params"}, + {"$ref": "#/definitions/offsetFromBottomMm"} + ] + } + } + }, + + + { + "description": "Pick up tip / drop tip / blowout commands", + "type": "object", + "required": ["command", "params"], + "additionalProperties": false, + "properties": { + "command": { + "enum": ["pick-up-tip", "drop-tip", "blowout"] }, "params": { "allOf": [