-
Notifications
You must be signed in to change notification settings - Fork 179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(protocol-designer): highlight tips per substep #2716
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,86 @@ | ||
// @flow | ||
import assert from 'assert' | ||
import last from 'lodash/last' | ||
import pick from 'lodash/pick' | ||
import type {Channels} from '@opentrons/components' | ||
import type {CommandCreator, RobotState, CommandCreatorError} from '../step-generation/types' | ||
import type { | ||
CommandCreator, | ||
CommandCreatorError, | ||
CommandsAndRobotState, | ||
RobotState, | ||
} from '../step-generation/types' | ||
|
||
import {getWellsForTips} from '../step-generation/utils' | ||
import type {SubstepTimelineFrame} from './types' | ||
import type {SubstepTimelineFrame, TipLocation} from './types' | ||
|
||
type SubstepTimeline = { | ||
function _conditionallyUpdateActiveTips (acc: SubstepTimelineAcc, nextFrame: CommandsAndRobotState) { | ||
const lastNewTipCommand = last(nextFrame.commands.filter(c => c.command === 'pick-up-tip')) | ||
const newTipParams = lastNewTipCommand && | ||
lastNewTipCommand.command === 'pick-up-tip' && | ||
lastNewTipCommand.params | ||
|
||
if (newTipParams) { | ||
return {...acc, prevActiveTips: {...newTipParams}} | ||
} | ||
return acc | ||
} | ||
|
||
type SubstepTimelineAcc = { | ||
timeline: Array<SubstepTimelineFrame>, | ||
errors: ?Array<CommandCreatorError>, | ||
prevActiveTips: ?TipLocation, | ||
prevRobotState: RobotState, | ||
} | ||
|
||
const substepTimelineSingle = (commandCreators: Array<CommandCreator>) => | ||
(initialRobotState: RobotState): Array<SubstepTimelineFrame> => { | ||
let prevRobotState = initialRobotState | ||
const timeline = commandCreators.reduce((acc: SubstepTimeline, commandCreator: CommandCreator, index: number) => { | ||
const timeline = commandCreators.reduce((acc: SubstepTimelineAcc, commandCreator: CommandCreator, index: number) => { | ||
// error short-circuit | ||
if (acc.errors) return acc | ||
|
||
const nextFrame = commandCreator(prevRobotState) | ||
const nextFrame = commandCreator(acc.prevRobotState) | ||
|
||
if (nextFrame.errors) { | ||
return {timeline: acc.timeline, errors: nextFrame.errors} | ||
return {...acc, errors: nextFrame.errors} | ||
} | ||
|
||
// NOTE: only aspirate and dispense commands will appear alone in atomic commands | ||
// from compound command creators (e.g. transfer, distribute, etc.) | ||
const isAtomic = nextFrame.commands.length === 1 | ||
const commandGroup = nextFrame.commands[0] | ||
if (isAtomic && (commandGroup.command === 'aspirate' || commandGroup.command === 'dispense')) { | ||
const firstCommand = nextFrame.commands[0] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if ( | ||
firstCommand.command === 'aspirate' || | ||
firstCommand.command === 'dispense') { | ||
assert(nextFrame.commands.length === 1, | ||
`substepTimeline expected nextFrame to have only single commands for ${firstCommand.command}`) | ||
|
||
const commandGroup = firstCommand | ||
const {well, volume, labware} = commandGroup.params | ||
const wellInfo = { | ||
labware, | ||
wells: [well], | ||
preIngreds: prevRobotState.liquidState.labware[labware][well], | ||
preIngreds: acc.prevRobotState.liquidState.labware[labware][well], | ||
postIngreds: nextFrame.robotState.liquidState.labware[labware][well], | ||
} | ||
prevRobotState = nextFrame.robotState | ||
const ingredKey = commandGroup.command === 'aspirate' ? 'source' : 'dest' | ||
return {timeline: [...acc.timeline, {volume, [ingredKey]: wellInfo}], errors: null} | ||
return { | ||
...acc, | ||
timeline: [ | ||
...acc.timeline, | ||
{ | ||
volume, | ||
[ingredKey]: wellInfo, | ||
activeTips: acc.prevActiveTips, | ||
}, | ||
], | ||
prevRobotState: nextFrame.robotState, | ||
} | ||
} else { | ||
return acc | ||
return { | ||
..._conditionallyUpdateActiveTips(acc, nextFrame), | ||
prevRobotState: nextFrame.robotState, | ||
} | ||
} | ||
}, {timeline: [], errors: null}) | ||
}, {timeline: [], errors: null, prevActiveTips: null, prevRobotState: initialRobotState}) | ||
|
||
return timeline.timeline | ||
} | ||
|
@@ -54,38 +93,57 @@ const substepTimeline = ( | |
if (context.channels === 1) { | ||
return substepTimelineSingle(commandCreators) | ||
} else { | ||
// timeline for multi-channel substep context | ||
return ( | ||
(initialRobotState: RobotState): Array<SubstepTimelineFrame> => { | ||
let prevRobotState = initialRobotState | ||
const timeline = commandCreators.reduce((acc: SubstepTimeline, commandCreator: CommandCreator, index: number) => { | ||
const timeline = commandCreators.reduce((acc: SubstepTimelineAcc, commandCreator: CommandCreator, index: number) => { | ||
// error short-circuit | ||
if (acc.errors) return acc | ||
|
||
const nextFrame = commandCreator(prevRobotState) | ||
const nextFrame = commandCreator(acc.prevRobotState) | ||
|
||
if (nextFrame.errors) { | ||
return {timeline: acc.timeline, errors: nextFrame.errors} | ||
return {...acc, errors: nextFrame.errors} | ||
} | ||
|
||
const isAtomic = nextFrame.commands.length === 1 | ||
const commandGroup = nextFrame.commands[0] | ||
if (isAtomic && (commandGroup.command === 'aspirate' || commandGroup.command === 'dispense')) { | ||
const {well, volume, labware} = commandGroup.params | ||
const firstCommand = nextFrame.commands[0] | ||
if ( | ||
firstCommand.command === 'aspirate' || | ||
firstCommand.command === 'dispense' | ||
) { | ||
assert(nextFrame.commands.length === 1, | ||
`substepTimeline expected nextFrame to have only single commands for ${firstCommand.command}`) | ||
|
||
const {well, volume, labware} = firstCommand.params | ||
const labwareType = context.getLabwareType && context.getLabwareType(labware) | ||
const wellsForTips = context.channels && labwareType && getWellsForTips(context.channels, labwareType, well).wellsForTips | ||
const wellInfo = { | ||
labware, | ||
wells: wellsForTips || [], | ||
preIngreds: wellsForTips ? pick(prevRobotState.liquidState.labware[labware], wellsForTips) : {}, | ||
preIngreds: wellsForTips ? pick(acc.prevRobotState.liquidState.labware[labware], wellsForTips) : {}, | ||
postIngreds: wellsForTips ? pick(nextFrame.robotState.liquidState.labware[labware], wellsForTips) : {}, | ||
} | ||
prevRobotState = nextFrame.robotState | ||
const ingredKey = commandGroup.command === 'aspirate' ? 'source' : 'dest' | ||
return {timeline: [...acc.timeline, {volume, [ingredKey]: wellInfo}], errors: null} | ||
|
||
const ingredKey = firstCommand.command === 'aspirate' ? 'source' : 'dest' | ||
return { | ||
...acc, | ||
timeline: [ | ||
...acc.timeline, | ||
{ | ||
volume, | ||
[ingredKey]: wellInfo, | ||
activeTips: acc.prevActiveTips, | ||
}, | ||
], | ||
prevRobotState: nextFrame.robotState, | ||
} | ||
} else { | ||
return acc | ||
return { | ||
..._conditionallyUpdateActiveTips(acc, nextFrame), | ||
prevRobotState: nextFrame.robotState, | ||
} | ||
} | ||
}, {timeline: [], errors: null}) | ||
}, {timeline: [], errors: null, prevActiveTips: null, prevRobotState: initialRobotState}) | ||
|
||
return timeline.timeline | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,8 +30,9 @@ export const allSubsteps: Selector<AllSubsteps> = createSelector( | |
robotStateTimeline, | ||
_initialRobotState, | ||
) => { | ||
const timeline = [{robotState: _initialRobotState}, ...robotStateTimeline.timeline] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this timeline was being created new inside each loop of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch |
||
|
||
return orderedSteps.reduce((acc: AllSubsteps, stepId, timelineIndex) => { | ||
const timeline = [{robotState: _initialRobotState}, ...robotStateTimeline.timeline] | ||
const robotState = timeline[timelineIndex] && timeline[timelineIndex].robotState | ||
|
||
const substeps = generateSubsteps( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎈