import chunk from 'lodash/chunk' ; import flatMap from 'lodash/flatMap' ; import { getWellDepth } from '@opentrons/shared-data' ; import { AIR_GAP_OFFSET_FROM_TOP } from '../../constants' ; import * as errorCreators from '../../errorCreators' ; import { getPipetteWithTipMaxVol } from '../../robotStateSelectors' ; import type { ConsolidateArgs , CommandCreator , CurriedCommandCreator , } from '../../types' ; import { blowoutUtil , curryCommandCreator , reduceCommandCreators , } from '../../utils' ; import { airGap , aspirate , delay , dispense , dropTip , moveToWell , replaceTip , touchTip , } from '../atomic' ; import { mixUtil } from './mix' ; export const consolidate = ( args , invariantContext , prevRobotState ) => { /**
      Consolidate will aspirate several times in sequence from multiple source wells,
      then dispense into a single destination.
       If the volume to aspirate from the source wells exceeds the max volume of the pipette,
      then consolidate will be broken up into multiple asp-asp-disp, asp-asp-disp cycles.
       A single uniform volume will be aspirated from every source well.
       =====
       For consolidate, changeTip means:
      * 'always': before the first aspirate in a single asp-asp-disp cycle, get a fresh tip
      * 'once': get a new tip at the beginning of the consolidate step, and use it throughout
      * 'never': reuse the tip from the last step
    */ const actionName = 'consolidate' ; const pipetteData = prevRobotState . pipettes [ args . pipette ] ; if ( ! pipetteData ) { return { errors : [ errorCreators . pipetteDoesNotExist ( { actionName , pipette : args . pipette , } ) , ] , } ; } const { aspirateDelay , aspirateFlowRateUlSec , aspirateOffsetFromBottomMm , blowoutFlowRateUlSec , blowoutOffsetFromTopMm , dispenseAirGapVolume , dispenseDelay , dispenseFlowRateUlSec , dispenseOffsetFromBottomMm , mixFirstAspirate , mixInDestination , } = args ; const aspirateAirGapVolume = args . aspirateAirGapVolume || 0 ; const maxWellsPerChunk = Math . floor ( getPipetteWithTipMaxVol ( args . pipette , invariantContext ) / ( args . volume + aspirateAirGapVolume ) ) ; const sourceLabwareDef = invariantContext . labwareEntities [ args . sourceLabware ] . def ; const destLabwareDef = invariantContext . labwareEntities [ args . destLabware ] . def ; const airGapOffsetDestWell = getWellDepth ( destLabwareDef , args . destWell ) + AIR_GAP_OFFSET_FROM_TOP ; const sourceWellChunks = chunk ( args . sourceWells , maxWellsPerChunk ) ; const commandCreators = flatMap ( sourceWellChunks , ( sourceWellChunk , chunkIndex ) : CurriedCommandCreator [ ] => { const isLastChunk = chunkIndex + 1 === sourceWellChunks . length ; const aspirateCommands = flatMap ( sourceWellChunk , ( sourceWell , wellIndex ) : CurriedCommandCreator [ ] => { const airGapOffsetSourceWell = getWellDepth ( sourceLabwareDef , sourceWell ) + AIR_GAP_OFFSET_FROM_TOP ; const airGapAfterAspirateCommands = aspirateAirGapVolume ? [ curryCommandCreator ( airGap , { pipette : args . pipette , volume : aspirateAirGapVolume , labware : args . sourceLabware , well : sourceWell , flowRate : aspirateFlowRateUlSec , offsetFromBottomMm : airGapOffsetSourceWell , } ) , ... ( aspirateDelay != null ? [ curryCommandCreator ( delay , { commandCreatorFnName : 'delay' , description : null , name : null , meta : null , wait : aspirateDelay . seconds , } ) , ] : [ ] ) , ] : [ ] ; const delayAfterAspirateCommands = aspirateDelay != null ? [ curryCommandCreator ( moveToWell , { pipette : args . pipette , labware : args . sourceLabware , well : sourceWell , offset : { x : 0 , y : 0 , z : aspirateDelay . mmFromBottom , } , } ) , curryCommandCreator ( delay , { commandCreatorFnName : 'delay' , description : null , name : null , meta : null , wait : aspirateDelay . seconds , } ) , ] : [ ] ; const touchTipAfterAspirateCommand = args . touchTipAfterAspirate ? [ curryCommandCreator ( touchTip , { pipette : args . pipette , labware : args . sourceLabware , well : sourceWell , offsetFromBottomMm : args . touchTipAfterAspirateOffsetMmFromBottom , } ) , ] : [ ] ; return [ curryCommandCreator ( aspirate , { pipette : args . pipette , volume : args . volume , labware : args . sourceLabware , well : sourceWell , flowRate : aspirateFlowRateUlSec , offsetFromBottomMm : aspirateOffsetFromBottomMm , } ) , ... delayAfterAspirateCommands , ... touchTipAfterAspirateCommand , ... airGapAfterAspirateCommands , ] ; } ) ; let tipCommands = [ ] ; if ( args . changeTip === 'always' || ( args . changeTip === 'once' && chunkIndex === 0 ) ) { tipCommands = [ curryCommandCreator ( replaceTip , { pipette : args . pipette , } ) , ] ; } const touchTipAfterDispenseCommands = args . touchTipAfterDispense ? [ curryCommandCreator ( touchTip , { pipette : args . pipette , labware : args . destLabware , well : args . destWell , offsetFromBottomMm : args . touchTipAfterDispenseOffsetMmFromBottom , } ) , ] : [ ] ; const mixBeforeCommands = mixFirstAspirate != null ? mixUtil ( { pipette : args . pipette , labware : args . sourceLabware , well : sourceWellChunk [ 0 ] , volume : mixFirstAspirate . volume , times : mixFirstAspirate . times , aspirateOffsetFromBottomMm , dispenseOffsetFromBottomMm : aspirateOffsetFromBottomMm , aspirateFlowRateUlSec , dispenseFlowRateUlSec , aspirateDelaySeconds : aspirateDelay ?. seconds , dispenseDelaySeconds : dispenseDelay ?. seconds , } ) : [ ] ; const preWetTipCommands = args . preWetTip ? mixUtil ( { pipette : args . pipette , labware : args . sourceLabware , well : sourceWellChunk [ 0 ] , volume : args . volume , times : 1 , aspirateOffsetFromBottomMm , dispenseOffsetFromBottomMm : aspirateOffsetFromBottomMm , aspirateFlowRateUlSec , dispenseFlowRateUlSec , aspirateDelaySeconds : aspirateDelay ?. seconds , dispenseDelaySeconds : dispenseDelay ?. seconds , } ) : [ ] ; const mixAfterCommands = mixInDestination != null ? mixUtil ( { pipette : args . pipette , labware : args . destLabware , well : args . destWell , volume : mixInDestination . volume , times : mixInDestination . times , aspirateOffsetFromBottomMm : dispenseOffsetFromBottomMm , dispenseOffsetFromBottomMm , aspirateFlowRateUlSec , dispenseFlowRateUlSec , aspirateDelaySeconds : aspirateDelay ?. seconds , dispenseDelaySeconds : dispenseDelay ?. seconds , } ) : [ ] ; const delayAfterDispenseCommands = dispenseDelay != null ? [ curryCommandCreator ( moveToWell , { pipette : args . pipette , labware : args . destLabware , well : args . destWell , offset : { x : 0 , y : 0 , z : dispenseDelay . mmFromBottom , } , } ) , curryCommandCreator ( delay , { commandCreatorFnName : 'delay' , description : null , name : null , meta : null , wait : dispenseDelay . seconds , } ) , ] : [ ] ; const willReuseTip = args . changeTip !== 'always' && ! isLastChunk ; const airGapAfterDispenseCommands = dispenseAirGapVolume && ! willReuseTip ? [ curryCommandCreator ( airGap , { pipette : args . pipette , volume : dispenseAirGapVolume , labware : args . destLabware , well : args . destWell , flowRate : aspirateFlowRateUlSec , offsetFromBottomMm : airGapOffsetDestWell , } ) , ... ( aspirateDelay != null ? [ curryCommandCreator ( delay , { commandCreatorFnName : 'delay' , description : null , name : null , meta : null , wait : aspirateDelay . seconds , } ) , ] : [ ] ) , ] : [ ] ; const dropTipAfterDispenseAirGap = airGapAfterDispenseCommands . length > 0 ? [ curryCommandCreator ( dropTip , { pipette : args . pipette , } ) , ] : [ ] ; const blowoutCommand = blowoutUtil ( { pipette : args . pipette , sourceLabwareId : args . sourceLabware , sourceWell : sourceWellChunk [ 0 ] , destLabwareId : args . destLabware , destWell : args . destWell , blowoutLocation : args . blowoutLocation , flowRate : blowoutFlowRateUlSec , offsetFromTopMm : blowoutOffsetFromTopMm , invariantContext , } ) ; return [ ... tipCommands , ... mixBeforeCommands , ... preWetTipCommands , ... aspirateCommands , curryCommandCreator ( dispense , { pipette : args . pipette , volume : args . volume * sourceWellChunk . length + aspirateAirGapVolume * sourceWellChunk . length , labware : args . destLabware , well : args . destWell , flowRate : dispenseFlowRateUlSec , offsetFromBottomMm : dispenseOffsetFromBottomMm , } ) , ... delayAfterDispenseCommands , ... mixAfterCommands , ... touchTipAfterDispenseCommands , ... blowoutCommand , ... airGapAfterDispenseCommands , ... dropTipAfterDispenseAirGap , ] ; } ) ; return reduceCommandCreators ( commandCreators , invariantContext , prevRobotState ) ; } ;