Skip to content

Commit

Permalink
feat(protocol-designer): timeline scrubber to the overview page
Browse files Browse the repository at this point in the history
  • Loading branch information
jerader committed Jan 17, 2025
1 parent 7b518e3 commit 48cc042
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@ export function ProtocolTimeline(): JSX.Element {

return storedProtocol != null && storedProtocol.mostRecentAnalysis != null ? (
<Box padding={SPACING.spacing16}>
<ProtocolTimelineScrubber
commands={storedProtocol.mostRecentAnalysis.commands}
analysis={storedProtocol.mostRecentAnalysis}
robotType={storedProtocol.mostRecentAnalysis.robotType}
/>
<ProtocolTimelineScrubber analysis={storedProtocol.mostRecentAnalysis} />
</Box>
) : (
<Icon size="8rem" name="ot-spinner" spin />
Expand Down
28 changes: 8 additions & 20 deletions components/src/organisms/ProtocolTimelineScrubber/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,8 @@ import type {
CompletedProtocolAnalysis,
LabwareLocation,
ProtocolAnalysisOutput,
RobotType,
RunTimeCommand,
} from '@opentrons/shared-data'
import type {
InvariantContext,
ModuleTemporalProperties,
TimelineFrame,
} from '@opentrons/step-generation'
import type { ModuleTemporalProperties } from '@opentrons/step-generation'
import type { LabwareOnDeck, Module } from '../..'

export * from './types'
Expand All @@ -56,13 +50,7 @@ const SEC_PER_FRAME = 1000
export const COMMAND_WIDTH_PX = 240

interface ProtocolTimelineScrubberProps {
commands: RunTimeCommand[]
analysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput
robotType?: RobotType
pdResource?: {
pdInvariantContext: InvariantContext
pdInitialRobotState: TimelineFrame
}
}

export const DECK_LAYER_BLOCKLIST = [
Expand All @@ -78,7 +66,8 @@ export const DECK_LAYER_BLOCKLIST = [
export function ProtocolTimelineScrubber(
props: ProtocolTimelineScrubberProps
): JSX.Element {
const { commands, analysis, robotType = FLEX_ROBOT_TYPE, pdResource } = props
const { analysis } = props
const { commands, robotType, liquids } = analysis
const wrapperRef = useRef<HTMLDivElement>(null)
const commandListRef = useRef<ViewportListRef>(null)
const [currentCommandIndex, setCurrentCommandIndex] = useState<number>(0)
Expand All @@ -90,8 +79,7 @@ export function ProtocolTimelineScrubber(
)
const { frame, invariantContext } = getResultingTimelineFrameFromRunCommands(
currentCommandsSlice,
pdResource?.pdInvariantContext ?? invariantContextFromRunCommands,
pdResource?.pdInitialRobotState
invariantContextFromRunCommands
)
const handlePlayPause = (): void => {
setIsPlaying(!isPlaying)
Expand All @@ -113,7 +101,7 @@ export function ProtocolTimelineScrubber(
}
}, [isPlaying, commands])

const robotState = frame.robotState
const { robotState } = frame

const [leftPipetteId] = Object.entries(robotState.pipettes).find(
([_pipetteId, pipette]) => pipette?.mount === 'left'
Expand All @@ -135,7 +123,7 @@ export function ProtocolTimelineScrubber(
invariantContext.labwareEntities,
robotState
)
const liquidDisplayColors = analysis.liquids.map(
const liquidDisplayColors = liquids.map(
liquid => liquid.displayColor ?? COLORS.blue50
)

Expand All @@ -154,7 +142,7 @@ export function ProtocolTimelineScrubber(
<Flex gridGap={SPACING.spacing8} flex="1 1 0">
<Flex height="40vh">
<BaseDeck
robotType={robotType}
robotType={robotType ?? FLEX_ROBOT_TYPE}
deckConfig={getSimplestDeckConfigForProtocol(analysis)}
modulesOnDeck={map(robotState.modules, (module, moduleId) => {
const labwareInModuleId =
Expand Down Expand Up @@ -303,7 +291,7 @@ export function ProtocolTimelineScrubber(
currentCommandIndex={currentCommandIndex}
setCurrentCommandIndex={setCurrentCommandIndex}
analysis={analysis}
robotType={robotType}
robotType={robotType ?? FLEX_ROBOT_TYPE}
allRunDefs={allRunDefs}
/>
)}
Expand Down
1 change: 0 additions & 1 deletion components/src/organisms/ProtocolTimelineScrubber/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import type {
import type {
LabwareEntities,
LocationLiquidState,
RunCommandTimelineFrame,
SingleLabwareLiquidState,
TimelineFrame,
} from '@opentrons/step-generation'
Expand Down
183 changes: 11 additions & 172 deletions protocol-designer/src/file-data/selectors/fileCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,21 @@ import { createSelector } from 'reselect'
import flatMap from 'lodash/flatMap'
import isEmpty from 'lodash/isEmpty'
import mapValues from 'lodash/mapValues'
import map from 'lodash/map'
import reduce from 'lodash/reduce'
import uniq from 'lodash/uniq'
import {
FLEX_ROBOT_TYPE,
OT2_STANDARD_DECKID,
OT2_STANDARD_MODEL,
FLEX_STANDARD_DECKID,
SPAN7_8_10_11_SLOT,
} from '@opentrons/shared-data'

import { COLUMN_4_SLOTS } from '@opentrons/step-generation'
import { selectors as dismissSelectors } from '../../dismiss'
import { selectors as labwareDefSelectors } from '../../labware-defs'
import { uuid } from '../../utils'
import { selectors as ingredSelectors } from '../../labware-ingred/selectors'
import { selectors as stepFormSelectors } from '../../step-forms'
import { selectors as uiLabwareSelectors } from '../../ui/labware'
import { swatchColors } from '../../organisms/DefineLiquidsModal/swatchColors'
import { getLoadLiquidCommands } from '../../load-file/migration/utils/getLoadLiquidCommands'
import {
DEFAULT_MM_FROM_BOTTOM_ASPIRATE,
DEFAULT_MM_FROM_BOTTOM_DISPENSE,
Expand All @@ -31,27 +26,21 @@ import {
import { getStepGroups } from '../../step-forms/selectors'
import { getFileMetadata, getRobotType } from './fileFields'
import { getInitialRobotState, getRobotStateTimeline } from './commands'
import { getLoadCommands } from './utils'

import type {
PipetteEntity,
LabwareEntities,
PipetteEntities,
RobotState,
} from '@opentrons/step-generation'
import type {
LabwareLocation,
AddressableAreaName,
CommandAnnotationV1Mixin,
CommandV8Mixin,
CreateCommand,
LabwareV2Mixin,
LiquidV1Mixin,
LoadLabwareCreateCommand,
LoadModuleCreateCommand,
LoadPipetteCreateCommand,
OT2RobotMixin,
OT3RobotMixin,
PipetteName,
ProtocolBase,
ProtocolFile,
} from '@opentrons/shared-data'
Expand Down Expand Up @@ -133,6 +122,16 @@ export const createFile: Selector<ProtocolFile> = createSelector(
) => {
const { author, description, created } = fileMetadata

const loadCommands = getLoadCommands(
initialRobotState,
pipetteEntities,
moduleEntities,
labwareEntities,
labwareNicknamesById,
ingredients,
ingredLocations
)

const name = fileMetadata.protocolName || 'untitled'
const lastModified = fileMetadata.lastModified
// TODO: Ian 2018-07-10 allow user to save steps in JSON file, even if those
Expand Down Expand Up @@ -168,39 +167,6 @@ export const createFile: Selector<ProtocolFile> = createSelector(
},
}

interface Pipettes {
[pipetteId: string]: { name: PipetteName }
}

const pipettes: Pipettes = mapValues(
initialRobotState.pipettes,
(
pipette: typeof initialRobotState.pipettes[keyof typeof initialRobotState.pipettes],
pipetteId: string
) => ({
name: pipetteEntities[pipetteId].name,
})
)

const loadPipetteCommands = map(
initialRobotState.pipettes,
(
pipette: typeof initialRobotState.pipettes[keyof typeof initialRobotState.pipettes],
pipetteId: string
): LoadPipetteCreateCommand => {
const loadPipetteCommand = {
key: uuid(),
commandType: 'loadPipette' as const,
params: {
pipetteName: pipettes[pipetteId].name,
mount: pipette.mount,
pipetteId: pipetteId,
},
}
return loadPipetteCommand
}
)

const liquids: ProtocolFile['liquids'] = reduce(
ingredients,
(acc, liquidData, liquidId) => {
Expand All @@ -215,139 +181,12 @@ export const createFile: Selector<ProtocolFile> = createSelector(
},
{}
)
// initiate "adapter" commands first so we can map through them to get the
// labware that goes on top of it's location
const loadAdapterCommands = reduce<
RobotState['labware'],
LoadLabwareCreateCommand[]
>(
initialRobotState.labware,
(
acc,
labware: typeof initialRobotState.labware[keyof typeof initialRobotState.labware],
labwareId: string
): LoadLabwareCreateCommand[] => {
const { def } = labwareEntities[labwareId]
const isAdapter = def.allowedRoles?.includes('adapter')
if (!isAdapter) return acc
const isOnTopOfModule = labware.slot in initialRobotState.modules
const namespace = def.namespace
const loadName = def.parameters.loadName
const version = def.version
const loadAdapterCommands = {
key: uuid(),
commandType: 'loadLabware' as const,
params: {
displayName: def.metadata.displayName,
labwareId,
loadName,
namespace: namespace,
version: version,
location: isOnTopOfModule
? { moduleId: labware.slot }
: { slotName: labware.slot },
},
}

return [...acc, loadAdapterCommands]
},
[]
)

const loadLabwareCommands = reduce<
RobotState['labware'],
LoadLabwareCreateCommand[]
>(
initialRobotState.labware,
(
acc,
labware: typeof initialRobotState.labware[keyof typeof initialRobotState.labware],
labwareId: string
): LoadLabwareCreateCommand[] => {
const { def } = labwareEntities[labwareId]
const isAdapter = def.allowedRoles?.includes('adapter')
if (isAdapter || def.metadata.displayCategory === 'trash') return acc
const isOnTopOfModule = labware.slot in initialRobotState.modules
const isOnAdapter =
loadAdapterCommands.find(
command => command.params.labwareId === labware.slot
) != null
const namespace = def.namespace
const loadName = def.parameters.loadName
const version = def.version
const isAddressableAreaName = COLUMN_4_SLOTS.includes(labware.slot)

let location: LabwareLocation = { slotName: labware.slot }
if (isOnTopOfModule) {
location = { moduleId: labware.slot }
} else if (isOnAdapter) {
location = { labwareId: labware.slot }
} else if (isAddressableAreaName) {
// TODO(bh, 2024-01-02): check slots against addressable areas via the deck definition
location = {
addressableAreaName: labware.slot as AddressableAreaName,
}
} else if (labware.slot === 'offDeck') {
location = 'offDeck'
}

const loadLabwareCommands = {
key: uuid(),
commandType: 'loadLabware' as const,
params: {
displayName:
labwareNicknamesById[labwareId] ?? def.metadata.displayName,
labwareId: labwareId,
loadName,
namespace: namespace,
version: version,
location,
},
}

return [...acc, loadLabwareCommands]
},
[]
)

const loadLiquidCommands = getLoadLiquidCommands(
ingredients,
ingredLocations
)
const loadModuleCommands = map(
initialRobotState.modules,
(
module: typeof initialRobotState.modules[keyof typeof initialRobotState.modules],
moduleId: string
): LoadModuleCreateCommand => {
const model = moduleEntities[moduleId].model
const loadModuleCommand = {
key: uuid(),
commandType: 'loadModule' as const,
params: {
model: model,
location: {
slotName: module.slot === SPAN7_8_10_11_SLOT ? '7' : module.slot,
},
moduleId: moduleId,
},
}
return loadModuleCommand
}
)

const labwareDefinitions = getLabwareDefinitionsInUse(
labwareEntities,
pipetteEntities,
labwareDefsByURI
)
const loadCommands: CreateCommand[] = [
...loadPipetteCommands,
...loadModuleCommands,
...loadAdapterCommands,
...loadLabwareCommands,
...loadLiquidCommands,
]

const nonLoadCommands: CreateCommand[] = flatMap(
robotStateTimeline.timeline,
Expand Down
Loading

0 comments on commit 48cc042

Please sign in to comment.