diff --git a/protocol-designer/src/components/StepEditForm/MixForm.js b/protocol-designer/src/components/StepEditForm/MixForm.js index ff722c8869b..74795d2581e 100644 --- a/protocol-designer/src/components/StepEditForm/MixForm.js +++ b/protocol-designer/src/components/StepEditForm/MixForm.js @@ -53,13 +53,15 @@ const MixForm = (props: MixFormProps): React.Element => { - + + +
- +
mixed } +type DP = { updateValue: (?number) => mixed } type OP = { mmFromBottom: number, wellHeightMM: number, isOpen: boolean, closeModal: () => mixed, - prefix: 'aspirate' | 'dispense', + defaultMm: number, + fieldName: TipOffsetFields, } type Props = OP & DP -type State = { value: string } +type State = { value: ?number } -const formatValue = (value: number | string): string => ( - String(round(Number(value), DECIMALS_ALLOWED)) +const roundValue = (value: number | string): number => ( + round(Number(value), DECIMALS_ALLOWED) ) class TipPositionModal extends React.Component { constructor (props: Props) { super(props) - this.state = { value: formatValue(props.mmFromBottom) } + const initialValue = props.mmFromBottom + ? roundValue(props.mmFromBottom) + : roundValue(this.getDefaultMmFromBottom()) + this.state = { value: initialValue } } componentDidUpdate (prevProps) { if (prevProps.wellHeightMM !== this.props.wellHeightMM) { - this.setState({value: formatValue(this.props.mmFromBottom)}) + this.setState({value: roundValue(this.props.mmFromBottom)}) } } applyChanges = () => { - this.props.updateValue(formatValue(this.state.value || 0)) + const {value} = this.state + console.log('applying changes', value) + this.props.updateValue(value == null ? null : roundValue(value)) + this.props.closeModal() + } + getDefaultMmFromBottom = (): number => { + const {fieldName, wellHeightMM} = this.props + return utils.getDefaultMmFromBottom({fieldName, wellHeightMM}) + } + getMinMaxMmFromBottom = (): {maxMmFromBottom: number, minMmFromBottom: number} => { + if (getIsTouchTipField(this.props.fieldName)) { + return { + maxMmFromBottom: roundValue(this.props.wellHeightMM), + minMmFromBottom: roundValue(this.props.wellHeightMM / 2), + } + } + return { + maxMmFromBottom: roundValue(this.props.wellHeightMM * 2), + minMmFromBottom: 0, + } } + handleReset = () => { - // NOTE: when `prefix` isn't set (eg in the Mix form), we'll use - // the value `DEFAULT_MM_FROM_BOTTOM_DISPENSE` (since we gotta pick something :/) - const defaultMm = this.props.prefix === 'aspirate' - ? DEFAULT_MM_FROM_BOTTOM_ASPIRATE - : DEFAULT_MM_FROM_BOTTOM_DISPENSE - this.setState({value: formatValue(defaultMm)}, this.applyChanges) - this.props.closeModal() + this.setState({value: null}, this.applyChanges) } handleCancel = () => { - this.setState({value: formatValue(this.props.mmFromBottom)}, this.applyChanges) - this.props.closeModal() + this.setState({value: roundValue(this.props.mmFromBottom)}, this.props.closeModal) } handleDone = () => { this.applyChanges() - this.props.closeModal() } - handleChange = (e: SyntheticEvent) => { - const {value} = e.currentTarget - const valueFloat = Number(formatValue(value)) - const maximumHeightMM = (this.props.wellHeightMM * 2) - if (!value) { - this.setState({value}) - } else if (valueFloat > maximumHeightMM) { - this.setState({value: formatValue(maximumHeightMM)}) - } else if (valueFloat >= 0) { - const numericValue = value.replace(/[^.0-9]/, '') - this.setState({value: numericValue.replace(/(\d*[.]{1}\d{1})(\d*)/, (match, group1) => group1)}) - } else { - this.setState({value: formatValue(0)}) + handleChange = (newValueRaw: string | number) => { + const {maxMmFromBottom, minMmFromBottom} = this.getMinMaxMmFromBottom() + // if string, strip non-number characters from string and cast to number + const valueFloatUnrounded = (typeof newValueRaw === 'string') + ? Number(newValueRaw + .replace(/[^.0-9]/, '') + .replace(/(\d*[.]{1}\d{1})(\d*)/, (match, group1) => group1)) + : newValueRaw + const valueFloat = roundValue(valueFloatUnrounded) + + if (!Number.isFinite(valueFloat)) { + return } + + this.setState({value: clamp(valueFloat, minMmFromBottom, maxMmFromBottom)}) } - makeHandleIncrement = (step: number) => () => { + handleInputFieldChange = (e: SyntheticEvent) => { + this.handleChange(e.currentTarget.value) + } + handleIncrementDecrement = (delta: number) => { const {value} = this.state - const incrementedValue = parseFloat(value || 0) + step - const maximumHeightMM = (this.props.wellHeightMM * 2) - this.setState({value: formatValue(Math.min(incrementedValue, maximumHeightMM))}) + const prevValue = this.state.value == null + ? this.getDefaultMmFromBottom() + : value + + this.handleChange(prevValue + delta) + } + makeHandleIncrement = (step: number) => () => { + this.handleIncrementDecrement(step) } makeHandleDecrement = (step: number) => () => { - const nextValueFloat = parseFloat(this.state.value || 0) - step - this.setState({value: formatValue(nextValueFloat < 0 ? 0 : nextValueFloat)}) + this.handleIncrementDecrement(step * -1) } render () { if (!this.props.isOpen) return null const {value} = this.state - const {wellHeightMM} = this.props + const {fieldName, wellHeightMM} = this.props + const {maxMmFromBottom, minMmFromBottom} = this.getMinMaxMmFromBottom() return ( @@ -122,33 +146,37 @@ class TipPositionModal extends React.Component { onCloseClick={this.handleCancel}>

{i18n.t('modal.tip_position.title')}

-

{i18n.t('modal.tip_position.body')}

+

{i18n.t(`modal.tip_position.body.${fieldName}`)}

+ value={(value != null) ? String(value) : ''} />
= (wellHeightMM * 2)} + disabled={value != null && value >= maxMmFromBottom} onClick={this.makeHandleIncrement(SMALL_STEP_MM)}>
- +
{/* TODO: xy tip positioning */}
@@ -174,14 +202,9 @@ class TipPositionModal extends React.Component { } const mapDTP = (dispatch: Dispatch, ownProps: OP): DP => { - // NOTE: not interpolating prefix because breaks flow string enum - - let fieldName = 'mmFromBottom' - if (ownProps.prefix === 'aspirate') fieldName = 'aspirate_mmFromBottom' - else if (ownProps.prefix === 'dispense') fieldName = 'dispense_mmFromBottom' return { updateValue: (value) => { - dispatch(actions.changeFormInput({update: {[fieldName]: value}})) + dispatch(actions.changeFormInput({update: {[ownProps.fieldName]: value}})) }, } } diff --git a/protocol-designer/src/components/StepEditForm/TipPositionInput/TipPositionZAxisViz.js b/protocol-designer/src/components/StepEditForm/TipPositionInput/TipPositionZAxisViz.js index 5b8a5d6a30a..2cee5075836 100644 --- a/protocol-designer/src/components/StepEditForm/TipPositionInput/TipPositionZAxisViz.js +++ b/protocol-designer/src/components/StepEditForm/TipPositionInput/TipPositionZAxisViz.js @@ -10,15 +10,15 @@ import styles from './TipPositionInput.css' const WELL_HEIGHT_PIXELS = 48 const PIXEL_DECIMALS = 2 type Props = { - mmFromBottom: string, + mmFromBottom: number, wellHeightMM: number, } const TipPositionZAxisViz = (props: Props) => { - const fractionOfWellHeight = Number(props.mmFromBottom) / props.wellHeightMM + const fractionOfWellHeight = props.mmFromBottom / props.wellHeightMM const pixelsFromBottom = (Number(fractionOfWellHeight) * WELL_HEIGHT_PIXELS) - WELL_HEIGHT_PIXELS - const roundedPixelsFromBottom = String(round(pixelsFromBottom, PIXEL_DECIMALS)) - const bottomPx = props.wellHeightMM ? roundedPixelsFromBottom : (parseFloat(props.mmFromBottom) - WELL_HEIGHT_PIXELS) + const roundedPixelsFromBottom = round(pixelsFromBottom, PIXEL_DECIMALS) + const bottomPx = props.wellHeightMM ? roundedPixelsFromBottom : (props.mmFromBottom - WELL_HEIGHT_PIXELS) return (
{ handleClose = () => { this.setState({isModalOpen: false}) } render () { + const {fieldName, mmFromBottom, wellHeightMM} = this.props + const disabled = !this.props.wellHeightMM + const isTouchTipField = getIsTouchTipField(this.props.fieldName) + + const Wrapper = ({children, hoverTooltipHandlers}) => isTouchTipField + ? {children} + : + {children} + + + let value = '' + if (wellHeightMM != null) { + // show default value for field in parens if no mmFromBottom value is selected + value = (mmFromBottom != null) + ? mmFromBottom + : `Default (${getDefaultMmFromBottom({fieldName, wellHeightMM})})` + } + return ( {(hoverTooltipHandlers) => ( - + - + )} ) } } + const mapSTP = (state: BaseState, ownProps: OP): SP => { const formData = selectors.getUnsavedForm(state) - // NOTE: not interpolating prefix because breaks flow string enum - let fieldName = 'mmFromBottom' - if (ownProps.prefix === 'aspirate') fieldName = 'aspirate_mmFromBottom' - else if (ownProps.prefix === 'dispense') fieldName = 'dispense_mmFromBottom' - - let labwareFieldName = 'labware' - if (ownProps.prefix === 'aspirate') labwareFieldName = 'aspirate_labware' - else if (ownProps.prefix === 'dispense') labwareFieldName = 'dispense_labware' + const {fieldName} = ownProps + const labwareFieldName = getLabwareFieldForPositioningField(ownProps.fieldName) let wellHeightMM = null if (formData && formData[labwareFieldName]) { @@ -79,6 +110,7 @@ const mapSTP = (state: BaseState, ownProps: OP): SP => { console.warn('the specified source labware definition could not be located') } } + return { wellHeightMM, mmFromBottom: formData && formData[fieldName], diff --git a/protocol-designer/src/components/StepEditForm/TipPositionInput/utils.js b/protocol-designer/src/components/StepEditForm/TipPositionInput/utils.js new file mode 100644 index 00000000000..fd043c64031 --- /dev/null +++ b/protocol-designer/src/components/StepEditForm/TipPositionInput/utils.js @@ -0,0 +1,31 @@ +// @flow +import assert from 'assert' +import { + DEFAULT_MM_FROM_BOTTOM_ASPIRATE, + DEFAULT_MM_FROM_BOTTOM_DISPENSE, + DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP, +} from '../../../constants' + +import {getIsTouchTipField, type TipOffsetFields} from '../../../form-types' + +export function getDefaultMmFromBottom (args: { + fieldName: TipOffsetFields, + wellHeightMM: number, +}): number { + const {fieldName, wellHeightMM} = args + switch (fieldName) { + case 'aspirate_mmFromBottom': + return DEFAULT_MM_FROM_BOTTOM_ASPIRATE + case 'dispense_mmFromBottom': + return DEFAULT_MM_FROM_BOTTOM_DISPENSE + case 'mix_mmFromBottom': + // TODO: Ian 2018-11-131 figure out what offset makes most sense for mix + return DEFAULT_MM_FROM_BOTTOM_DISPENSE + default: + // touch tip fields + assert( + getIsTouchTipField(fieldName), + `getDefaultMmFromBottom fn does not know what to do with field ${fieldName}`) + return DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP + wellHeightMM + } +} diff --git a/protocol-designer/src/components/StepEditForm/TransferLikeForm.js b/protocol-designer/src/components/StepEditForm/TransferLikeForm.js index 53c574342de..8d059cb34a2 100644 --- a/protocol-designer/src/components/StepEditForm/TransferLikeForm.js +++ b/protocol-designer/src/components/StepEditForm/TransferLikeForm.js @@ -53,7 +53,9 @@ const TransferLikeForm = (props: TransferLikeFormProps) => {
- + + + @@ -93,7 +95,7 @@ const TransferLikeForm = (props: TransferLikeFormProps) => {
- +
{stepType !== 'distribute' && } @@ -135,7 +137,9 @@ const TransferLikeForm = (props: TransferLikeFormProps) => {
- + + + @@ -152,7 +156,7 @@ const TransferLikeForm = (props: TransferLikeFormProps) => {
- +
{stepType !== 'consolidate' && } diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js index 9fb8f8da0ec..45510f59215 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionInput.js @@ -8,9 +8,8 @@ import {selectors as steplistSelectors, actions as steplistActions} from '../../ import styles from '../StepEditForm.css' import type {Dispatch} from 'redux' -import type {StepFieldName} from '../../../steplist/fieldLevel' +import type {StepIdType, StepFieldName} from '../../../form-types' import type {BaseState} from '../../../types' -import type {StepIdType} from '../../../form-types' import type { FocusHandlers } from '../index' type SP = { diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js index 805b3f1d167..98dc2ed1a2d 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/WellSelectionModal.js @@ -16,7 +16,7 @@ import type {Wells, ContentsByWell} from '../../../labware-ingred/types' import {selectors as steplistSelectors} from '../../../steplist' import type {WellIngredientNames} from '../../../steplist/types' import {changeFormInput} from '../../../steplist/actions' -import type {StepFieldName} from '../../../steplist/fieldLevel' +import type {StepFieldName} from '../../../form-types' import type {PipetteData} from '../../../step-generation/types' import {SelectableLabware} from '../../labware' diff --git a/protocol-designer/src/components/StepEditForm/WellSelectionInput/index.js b/protocol-designer/src/components/StepEditForm/WellSelectionInput/index.js index 977c218a826..dd524ac1212 100644 --- a/protocol-designer/src/components/StepEditForm/WellSelectionInput/index.js +++ b/protocol-designer/src/components/StepEditForm/WellSelectionInput/index.js @@ -4,9 +4,10 @@ import WellSelectionInput from './WellSelectionInput' import {connect} from 'react-redux' import {selectors as pipetteSelectors} from '../../../pipettes' import {selectors as steplistSelectors} from '../../../steplist' -import {getFieldErrors, type StepFieldName} from '../../../steplist/fieldLevel' -import type {BaseState, ThunkDispatch} from '../../../types' +import {getFieldErrors} from '../../../steplist/fieldLevel' import {showFieldErrors} from '../StepFormField' +import type {BaseState, ThunkDispatch} from '../../../types' +import type {StepFieldName} from '../../../form-types' import type {FocusHandlers} from '../index' type Props = React.ElementProps diff --git a/protocol-designer/src/components/StepEditForm/index.js b/protocol-designer/src/components/StepEditForm/index.js index ec1a82a2482..340836b98c1 100644 --- a/protocol-designer/src/components/StepEditForm/index.js +++ b/protocol-designer/src/components/StepEditForm/index.js @@ -6,8 +6,7 @@ import without from 'lodash/without' import cx from 'classnames' import {actions, selectors} from '../../steplist' -import type {StepFieldName} from '../../steplist/fieldLevel' -import type {FormData, StepType} from '../../form-types' +import type {FormData, StepType, StepFieldName} from '../../form-types' import type {BaseState, ThunkDispatch} from '../../types' import formStyles from '../forms.css' import styles from './StepEditForm.css' diff --git a/protocol-designer/src/form-types.js b/protocol-designer/src/form-types.js index 3e75bb2170e..bed205efe22 100644 --- a/protocol-designer/src/form-types.js +++ b/protocol-designer/src/form-types.js @@ -1,10 +1,61 @@ // @flow import type {IconName} from '@opentrons/components' import type {ChangeTipOptions} from './step-generation' -import type {StepFieldName} from './steplist/fieldLevel' export type StepIdType = string +export type StepFieldName = + | 'aspirate_airGap_checkbox' + | 'aspirate_airGap_volume' + | 'aspirate_changeTip' + | 'aspirate_disposalVol_checkbox' + | 'aspirate_disposalVol_destination' + | 'aspirate_disposalVol_volume' + | 'aspirate_flowRate' + | 'aspirate_labware' + | 'aspirate_mix_checkbox' + | 'aspirate_mix_times' + | 'aspirate_mix_volume' + | 'aspirate_preWetTip' + | 'aspirate_touchTip' + | 'aspirate_touchTipMmFromBottom' + | 'aspirate_mmFromBottom' + | 'aspirate_wellOrder_first' + | 'aspirate_wellOrder_second' + | 'aspirate_wells' + | 'changeTip' + | 'dispense_blowout_checkbox' + | 'dispense_blowout_labware' + | 'dispense_delay_checkbox' + | 'dispense_delayMinutes' + | 'dispense_delaySeconds' + | 'dispense_flowRate' + | 'dispense_labware' + | 'dispense_touchTip' + | 'dispense_mix_checkbox' + | 'dispense_mix_times' + | 'dispense_mix_volume' + | 'dispense_touchTipMmFromBottom' + | 'dispense_mmFromBottom' + | 'dispense_wellOrder_first' + | 'dispense_wellOrder_second' + | 'dispense_wells' + | 'labware' + | 'pauseForAmountOfTime' + | 'pauseHour' + | 'pauseMessage' + | 'pauseMinute' + | 'pauseSecond' + | 'pipette' + | 'stepDetails' + | 'stepName' + | 'times' + | 'mix_mmFromBottom' + | 'mix_touchTipMmFromBottom' + | 'touchTip' + | 'volume' + | 'wells' + // TODO Ian 2018-01-16 factor out to some constants.js ? export const stepIconsByType: {[string]: IconName} = { 'transfer': 'ot-transfer', @@ -38,10 +89,6 @@ export type ChangeTipFields = {| 'aspirate_changeTip'?: ChangeTipOptions, |} -export type TouchTipFields = {| - 'aspirate_touchTip'?: boolean, -|} - export type TransferLikeStepType = 'transfer' | 'consolidate' | 'distribute' export type TransferLikeForm = {| @@ -49,7 +96,6 @@ export type TransferLikeForm = {| ...BlowoutFields, ...ChangeTipFields, ...DelayFields, - ...TouchTipFields, stepType: TransferLikeStepType, id: StepIdType, @@ -80,7 +126,6 @@ export type MixForm = {| ...BlowoutFields, ...ChangeTipFields, ...DelayFields, - ...TouchTipFields, stepType: 'mix', id: StepIdType, @@ -121,3 +166,20 @@ export type BlankForm = { stepType: StepType, id: StepIdType, } + +// fields used in TipPositionInput +export type TipOffsetFields = 'aspirate_mmFromBottom' + | 'dispense_mmFromBottom' + | 'mix_mmFromBottom' + | 'aspirate_touchTipMmFromBottom' + | 'dispense_touchTipMmFromBottom' + | 'mix_touchTipMmFromBottom' + +export function getIsTouchTipField (fieldName: string): boolean { + const touchTipFields = [ + 'aspirate_touchTipMmFromBottom', + 'dispense_touchTipMmFromBottom', + 'mix_touchTipMmFromBottom', + ] + return touchTipFields.includes(fieldName) +} diff --git a/protocol-designer/src/localization/en/modal.json b/protocol-designer/src/localization/en/modal.json index 64fc56d6ef4..240ca12d4f5 100644 --- a/protocol-designer/src/localization/en/modal.json +++ b/protocol-designer/src/localization/en/modal.json @@ -4,7 +4,14 @@ }, "tip_position": { "title": "Tip Positioning", - "body": "Change from where in the well the robot aspirates", + "body": { + "aspirate_mmFromBottom": "Change from where in the well the robot aspirates", + "dispense_mmFromBottom": "Change from where in the well the robot dispenses", + "mix_mmFromBottom": "Change from where in the well the robot aspirates and dispenses during the mix", + "aspirate_touchTipMmFromBottom": "Change from where in the well the robot performs touch tip", + "dispense_touchTipMmFromBottom": "Change from where in the well the robot performs touch tip", + "mix_touchTipMmFromBottom": "Change from where in the well the robot performs touch tip" + }, "field_label": "Distance from bottom of well" }, "well_order": { @@ -36,4 +43,4 @@ } } } -} \ No newline at end of file +} diff --git a/protocol-designer/src/step-generation/consolidate.js b/protocol-designer/src/step-generation/consolidate.js index 856778d0137..4a02d453123 100644 --- a/protocol-designer/src/step-generation/consolidate.js +++ b/protocol-designer/src/step-generation/consolidate.js @@ -61,6 +61,7 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev pipette: data.pipette, labware: data.sourceLabware, well: sourceWell, + offsetFromBottomMm: data.touchTipAfterAspirateOffsetMmFromBottom, })] : [] @@ -90,6 +91,7 @@ const consolidate = (data: ConsolidateFormData): CompoundCommandCreator => (prev pipette: data.pipette, labware: data.destLabware, well: data.destWell, + offsetFromBottomMm: data.touchTipAfterDispenseOffsetMmFromBottom, })] : [] diff --git a/protocol-designer/src/step-generation/distribute.js b/protocol-designer/src/step-generation/distribute.js index 5124e9a8d04..a1e76cac155 100644 --- a/protocol-designer/src/step-generation/distribute.js +++ b/protocol-designer/src/step-generation/distribute.js @@ -85,6 +85,7 @@ const distribute = (data: DistributeFormData): CompoundCommandCreator => (prevRo pipette, labware: data.destLabware, well: destWell, + offsetFromBottomMm: data.touchTipAfterDispenseOffsetMmFromBottom, }), ] : [] @@ -126,6 +127,7 @@ const distribute = (data: DistributeFormData): CompoundCommandCreator => (prevRo pipette: data.pipette, labware: data.sourceLabware, well: data.sourceWell, + offsetFromBottomMm: data.touchTipAfterDispenseOffsetMmFromBottom, }), ] : [] diff --git a/protocol-designer/src/step-generation/mix.js b/protocol-designer/src/step-generation/mix.js index f80ef9c0265..f324e59a8e0 100644 --- a/protocol-designer/src/step-generation/mix.js +++ b/protocol-designer/src/step-generation/mix.js @@ -77,7 +77,14 @@ const mix = (data: MixFormData): CompoundCommandCreator => (prevRobotState: Robo } const touchTipCommands = data.touchTip - ? [touchTip({pipette, labware, well})] + ? [ + touchTip({ + pipette, + labware, + well, + offsetFromBottomMm: data.touchTipMmFromBottom, + }), + ] : [] const blowoutCommands = data.blowout diff --git a/protocol-designer/src/step-generation/transfer.js b/protocol-designer/src/step-generation/transfer.js index 7fa21e9bbed..d4489bfdd80 100644 --- a/protocol-designer/src/step-generation/transfer.js +++ b/protocol-designer/src/step-generation/transfer.js @@ -102,6 +102,7 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS pipette: data.pipette, labware: data.sourceLabware, well: sourceWell, + offsetFromBottomMm: data.touchTipAfterAspirateOffsetMmFromBottom, })] : [] @@ -110,6 +111,7 @@ const transfer = (data: TransferFormData): CompoundCommandCreator => (prevRobotS pipette: data.pipette, labware: data.destLabware, well: destWell, + offsetFromBottomMm: data.touchTipAfterDispenseOffsetMmFromBottom, })] : [] diff --git a/protocol-designer/src/step-generation/types.js b/protocol-designer/src/step-generation/types.js index 1039c8f2b6c..c006707f645 100644 --- a/protocol-designer/src/step-generation/types.js +++ b/protocol-designer/src/step-generation/types.js @@ -34,6 +34,8 @@ export type TransferLikeFormDataFields = { preWetTip: boolean, /** Touch tip after every aspirate */ touchTipAfterAspirate: boolean, + /** Optional offset for touch tip after aspirate (if null, use PD default) */ + touchTipAfterAspirateOffsetMmFromBottom?: ?number, /** changeTip is interpreted differently by different Step types */ changeTip: ChangeTipOptions, /** Disposal volume is added to the volume of the first aspirate of each asp-asp-disp cycle */ @@ -44,6 +46,8 @@ export type TransferLikeFormDataFields = { // ===== DISPENSE SETTINGS ===== /** Touch tip in destination well after dispense */ touchTipAfterDispense: boolean, + /** Optional offset for touch tip after dispense (if null, use PD default) */ + touchTipAfterDispenseOffsetMmFromBottom?: ?number, /** Number of seconds to delay at the very end of the step (TODO: or after each dispense ?) */ delayAfterDispense: ?number, /** offset from bottom of well in mm */ @@ -104,6 +108,7 @@ export type MixFormData = { times: number, /** Touch tip after mixing */ touchTip: boolean, + touchTipMmFromBottom?: ?number, /** Delay in seconds */ delay: ?number, /** change tip: see comments in step-generation/mix.js */ diff --git a/protocol-designer/src/steplist/actions/handleFormChange.js b/protocol-designer/src/steplist/actions/handleFormChange.js index 88e4c5fc018..56d38a2732c 100644 --- a/protocol-designer/src/steplist/actions/handleFormChange.js +++ b/protocol-designer/src/steplist/actions/handleFormChange.js @@ -3,10 +3,6 @@ import uniq from 'lodash/uniq' import {getWellSetForMultichannel} from '../../well-selection/utils' import {selectors} from '../index' import {selectors as pipetteSelectors} from '../../pipettes' -import { - DEFAULT_MM_FROM_BOTTOM_ASPIRATE, - DEFAULT_MM_FROM_BOTTOM_DISPENSE, -} from '../../constants' import {selectors as labwareIngredSelectors} from '../../labware-ingred/reducers' import type {PipetteChannels} from '@opentrons/shared-data' import type {BaseState, GetState} from '../../types' @@ -89,7 +85,8 @@ export const getChangeLabwareEffects = (updateFormData: {[StepFieldName]: ?mixed updateOverrides = { ...updateOverrides, 'aspirate_wells': null, - 'aspirate_mmFromBottom': DEFAULT_MM_FROM_BOTTOM_ASPIRATE, + 'aspirate_mmFromBottom': null, + 'aspirate_touchTipMmFromBottom': null, } } // Changing labware clears wells selection: dest labware @@ -97,7 +94,8 @@ export const getChangeLabwareEffects = (updateFormData: {[StepFieldName]: ?mixed updateOverrides = { ...updateOverrides, 'dispense_wells': null, - 'dispense_mmFromBottom': DEFAULT_MM_FROM_BOTTOM_DISPENSE, + 'dispense_mmFromBottom': null, + 'dispense_touchTipMmFromBottom': null, } } // Changing labware clears wells selection: labware (eg, mix) @@ -105,9 +103,8 @@ export const getChangeLabwareEffects = (updateFormData: {[StepFieldName]: ?mixed updateOverrides = { ...updateOverrides, 'wells': null, - // TODO: Ian 2018-09-03 should we have both asp/disp for Mix? - // if not, is dispense the right choice vs aspirate? - 'dispense_mmFromBottom': DEFAULT_MM_FROM_BOTTOM_DISPENSE, + 'mix_mmFromBottom': null, + 'mix_touchTipMmFromBottom': null, } } return updateOverrides diff --git a/protocol-designer/src/steplist/fieldLevel/index.js b/protocol-designer/src/steplist/fieldLevel/index.js index 071193a2252..c0c763fc6cd 100644 --- a/protocol-designer/src/steplist/fieldLevel/index.js +++ b/protocol-designer/src/steplist/fieldLevel/index.js @@ -16,7 +16,7 @@ import { composeProcessors, type ValueProcessor, } from './processing' -import type {StepFieldName} from './types' +import type {StepFieldName} from '../../form-types' import type {StepFormContextualState} from '../types' export type { diff --git a/protocol-designer/src/steplist/fieldLevel/types.js b/protocol-designer/src/steplist/fieldLevel/types.js deleted file mode 100644 index 791f8269f62..00000000000 --- a/protocol-designer/src/steplist/fieldLevel/types.js +++ /dev/null @@ -1,50 +0,0 @@ -// @flow - -export type StepFieldName = - | 'aspirate_airGap_checkbox' - | 'aspirate_airGap_volume' - | 'aspirate_changeTip' - | 'aspirate_disposalVol_checkbox' - | 'aspirate_disposalVol_destination' - | 'aspirate_disposalVol_volume' - | 'aspirate_flowRate' - | 'aspirate_labware' - | 'aspirate_mix_checkbox' - | 'aspirate_mix_times' - | 'aspirate_mix_volume' - | 'aspirate_preWetTip' - | 'aspirate_touchTip' - | 'aspirate_mmFromBottom' - | 'aspirate_wellOrder_first' - | 'aspirate_wellOrder_second' - | 'aspirate_wells' - | 'changeTip' - | 'dispense_blowout_checkbox' - | 'dispense_blowout_labware' - | 'dispense_delay_checkbox' - | 'dispense_delayMinutes' - | 'dispense_delaySeconds' - | 'dispense_flowRate' - | 'dispense_labware' - | 'dispense_touchTip' - | 'dispense_mix_checkbox' - | 'dispense_mix_times' - | 'dispense_mix_volume' - | 'dispense_mmFromBottom' - | 'dispense_wellOrder_first' - | 'dispense_wellOrder_second' - | 'dispense_wells' - | 'labware' - | 'pauseForAmountOfTime' - | 'pauseHour' - | 'pauseMessage' - | 'pauseMinute' - | 'pauseSecond' - | 'pipette' - | 'stepDetails' - | 'stepName' - | 'times' - | 'mmFromBottom' - | 'touchTip' - | 'volume' - | 'wells' diff --git a/protocol-designer/src/steplist/formLevel/errors.js b/protocol-designer/src/steplist/formLevel/errors.js index 85f77937a41..c5d69dbee08 100644 --- a/protocol-designer/src/steplist/formLevel/errors.js +++ b/protocol-designer/src/steplist/formLevel/errors.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react' import {canPipetteUseLabware} from '@opentrons/shared-data' -import type {StepFieldName} from '../fieldLevel' +import type {StepFieldName} from '../../form-types' /******************* ** Error Messages ** diff --git a/protocol-designer/src/steplist/formLevel/getDefaultsForStepType.js b/protocol-designer/src/steplist/formLevel/getDefaultsForStepType.js index 7c89e25f802..48e14bd2a47 100644 --- a/protocol-designer/src/steplist/formLevel/getDefaultsForStepType.js +++ b/protocol-designer/src/steplist/formLevel/getDefaultsForStepType.js @@ -46,7 +46,7 @@ export default function getDefaultsForStepType (stepType: StepType) { 'aspirate_wellOrder_first': DEFAULT_WELL_ORDER_FIRST_OPTION, 'aspirate_wellOrder_second': DEFAULT_WELL_ORDER_SECOND_OPTION, 'wells': [], - 'mmFromBottom': DEFAULT_MM_FROM_BOTTOM_DISPENSE, // NOTE: mix uses dispense for both asp + disp, for now + 'mix_mmFromBottom': DEFAULT_MM_FROM_BOTTOM_DISPENSE, // NOTE: mix uses dispense for both asp + disp, for now 'volume': undefined, } case 'distribute': diff --git a/protocol-designer/src/steplist/formLevel/stepFormToArgs/mixFormToArgs.js b/protocol-designer/src/steplist/formLevel/stepFormToArgs/mixFormToArgs.js index 1d2329a6fe5..3dac3e8792d 100644 --- a/protocol-designer/src/steplist/formLevel/stepFormToArgs/mixFormToArgs.js +++ b/protocol-designer/src/steplist/formLevel/stepFormToArgs/mixFormToArgs.js @@ -13,6 +13,7 @@ type MixStepArgs = MixFormData const mixFormToArgs = (hydratedFormData: FormData): MixStepArgs => { const {labware, pipette} = hydratedFormData const touchTip = !!hydratedFormData['touchTip'] + const touchTipMmFromBottom = hydratedFormData['mix_touchTipMmFromBottom'] let wells = hydratedFormData.wells || [] const orderFirst = hydratedFormData.aspirate_wellOrder_first @@ -30,8 +31,8 @@ const mixFormToArgs = (hydratedFormData: FormData): MixStepArgs => { const times = Number(hydratedFormData.times) || 0 // NOTE: for mix, there is only one tip offset field, // and it applies to both aspirate and dispense - const aspirateOffsetFromBottomMm = Number(hydratedFormData['mmFromBottom']) - const dispenseOffsetFromBottomMm = Number(hydratedFormData['mmFromBottom']) + const aspirateOffsetFromBottomMm = Number(hydratedFormData['mix_mmFromBottom']) + const dispenseOffsetFromBottomMm = Number(hydratedFormData['mix_mmFromBottom']) // It's radiobutton, so one should always be selected. const changeTip = hydratedFormData['aspirate_changeTip'] || DEFAULT_CHANGE_TIP_OPTION @@ -53,6 +54,7 @@ const mixFormToArgs = (hydratedFormData: FormData): MixStepArgs => { volume, times, touchTip, + touchTipMmFromBottom, delay, changeTip, blowout: blowoutLabwareId, diff --git a/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js b/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js index b3801043fef..735ca95a7c9 100644 --- a/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js +++ b/protocol-designer/src/steplist/formLevel/stepFormToArgs/transferLikeFormToArgs.js @@ -26,6 +26,7 @@ type TransferLikeStepArgs = ConsolidateFormData | DistributeFormData | TransferF // TODO: BC 2018-10-30 move getting labwareDef into hydration layer upstream const transferLikeFormToArgs = (hydratedFormData: FormData): TransferLikeStepArgs => { + console.log([hydratedFormData]) const stepType = hydratedFormData.stepType const pipette = hydratedFormData['pipette'] const volume = Number(hydratedFormData['volume']) @@ -33,8 +34,17 @@ const transferLikeFormToArgs = (hydratedFormData: FormData): TransferLikeStepArg const destLabware = hydratedFormData['dispense_labware'] const blowoutLabwareId = hydratedFormData['dispense_blowout_checkbox'] ? hydratedFormData['dispense_blowout_labware'] : null - const aspirateOffsetFromBottomMm = Number(hydratedFormData['aspirate_mmFromBottom']) - const dispenseOffsetFromBottomMm = Number(hydratedFormData['dispense_mmFromBottom']) + const aspirateOffsetFromBottomMm = hydratedFormData['aspirate_mmFromBottom'] + const dispenseOffsetFromBottomMm = hydratedFormData['dispense_mmFromBottom'] + + const touchTipAfterAspirate = hydratedFormData['aspirate_touchTip'] || false + const touchTipAfterAspirateOffsetMmFromBottom = touchTipAfterAspirate + ? hydratedFormData['aspirate_touchTipMmFromBottom'] + : null + const touchTipAfterDispense = hydratedFormData['dispense_touchTip'] || false + const touchTipAfterDispenseOffsetMmFromBottom = touchTipAfterDispense + ? hydratedFormData['dispense_touchTipMmFromBottom'] + : null const delayAfterDispense = hydratedFormData['dispense_delay_checkbox'] ? ((Number(hydratedFormData['dispense_delayMinutes']) || 0) * 60) + @@ -79,8 +89,10 @@ const transferLikeFormToArgs = (hydratedFormData: FormData): TransferLikeStepArg delayAfterDispense, mixInDestination, preWetTip: hydratedFormData['aspirate_preWetTip'] || false, - touchTipAfterAspirate: hydratedFormData['aspirate_touchTip'] || false, - touchTipAfterDispense: hydratedFormData['dispense_touchTip'] || false, + touchTipAfterAspirate, + touchTipAfterAspirateOffsetMmFromBottom, + touchTipAfterDispense, + touchTipAfterDispenseOffsetMmFromBottom, description: 'description would be here 2018-03-01', // TODO get from form } diff --git a/protocol-designer/src/steplist/formLevel/warnings.js b/protocol-designer/src/steplist/formLevel/warnings.js index 7a34c51dc72..b1c4d868553 100644 --- a/protocol-designer/src/steplist/formLevel/warnings.js +++ b/protocol-designer/src/steplist/formLevel/warnings.js @@ -1,7 +1,7 @@ // @flow import * as React from 'react' import {getWellTotalVolume} from '@opentrons/shared-data' -import type {StepFieldName} from '../fieldLevel' +import type {StepFieldName} from '../../form-types' import KnowledgeBaseLink from '../../components/KnowledgeBaseLink' export const DISPOSAL_PERCENTAGE = 0.2 // 20% percent of pipette capacity diff --git a/protocol-designer/src/steplist/types.js b/protocol-designer/src/steplist/types.js index dc0e707afac..eaf8c02c9a5 100644 --- a/protocol-designer/src/steplist/types.js +++ b/protocol-designer/src/steplist/types.js @@ -1,9 +1,14 @@ // @flow import type {PauseFormData, CommandCreatorData} from '../step-generation' -import type {FormData, StepIdType, StepType, TransferLikeStepType} from '../form-types' +import type { + FormData, + StepIdType, + StepFieldName, + StepType, + TransferLikeStepType, +} from '../form-types' import type {BaseState} from '../types' import type {FormError} from './formLevel/errors' -import type {StepFieldName} from './fieldLevel' // sections of the form that are expandable/collapsible export type FormSectionState = {aspirate: boolean, dispense: boolean} diff --git a/protocol-designer/src/well-selection/actions.js b/protocol-designer/src/well-selection/actions.js index c1fd9d78f05..a0976021958 100644 --- a/protocol-designer/src/well-selection/actions.js +++ b/protocol-designer/src/well-selection/actions.js @@ -7,8 +7,8 @@ import {selectors as steplistSelectors} from '../steplist' import {selectors as pipetteSelectors} from '../pipettes' import {selectors as labwareIngredSelectors} from '../labware-ingred/reducers' -import type {StepFieldName} from '../steplist/fieldLevel' import type {ThunkDispatch, GetState} from '../types' +import type {StepFieldName} from '../form-types' import type {Wells} from '../labware-ingred/types' import type {Channels} from '@opentrons/components'