diff --git a/app/src/assets/images/labware/opentrons_flex_deck_riser.png b/app/src/assets/images/labware/opentrons_flex_deck_riser.png
new file mode 100644
index 00000000000..a06f26bf445
Binary files /dev/null and b/app/src/assets/images/labware/opentrons_flex_deck_riser.png differ
diff --git a/app/src/assets/images/labware/opentrons_tough_pcr_auto_sealing_lid.png b/app/src/assets/images/labware/opentrons_tough_pcr_auto_sealing_lid.png
new file mode 100644
index 00000000000..bc0cffa3df6
Binary files /dev/null and b/app/src/assets/images/labware/opentrons_tough_pcr_auto_sealing_lid.png differ
diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json
index f2e284e607e..94e679b4aba 100644
--- a/app/src/assets/localization/en/protocol_setup.json
+++ b/app/src/assets/localization/en/protocol_setup.json
@@ -120,6 +120,7 @@
"labware_position_check_step_description": "Recommended workflow that helps you verify the position of each labware on the deck.",
"labware_position_check_step_title": "Labware Position Check",
"labware_position_check_text": "Labware Position Check is a recommended workflow that helps you verify the position of each labware on the deck. During this check, you can create Labware Offsets that adjust how the robot moves to each labware in the X, Y and Z directions.",
+ "labware_quantity": "Quantity: {{quantity}}",
"labware_setup_step_description": "Gather the following labware and full tip racks. To run your protocol without Labware Position Check, place and secure labware in their initial locations.",
"labware_setup_step_title": "Labware",
"last_calibrated": "Last calibrated: {{date}}",
diff --git a/app/src/molecules/LabwareStackModal/LabwareStackModal.tsx b/app/src/molecules/LabwareStackModal/LabwareStackModal.tsx
index 9c6602023a8..83b588f4a6e 100644
--- a/app/src/molecules/LabwareStackModal/LabwareStackModal.tsx
+++ b/app/src/molecules/LabwareStackModal/LabwareStackModal.tsx
@@ -32,6 +32,8 @@ import {
THERMOCYCLER_MODULE_TYPE,
} from '@opentrons/shared-data'
import tiprackAdapter from '/app/assets/images/labware/opentrons_flex_96_tiprack_adapter.png'
+import tcLid from '/app/assets/images/labware/opentrons_tough_pcr_auto_sealing_lid.png'
+import deckRiser from '/app/assets/images/labware/opentrons_flex_deck_riser.png'
import type { RobotType, RunTimeCommand } from '@opentrons/shared-data'
@@ -58,6 +60,13 @@ const LIST_ITEM_STYLE = css`
justify-content: ${JUSTIFY_SPACE_BETWEEN};
`
+const ADAPTER_LOAD_NAMES_TO_SHOW_IMAGE: { [key: string]: string } = {
+ opentrons_flex_96_tiprack_adapter: tiprackAdapter,
+ opentrons_flex_deck_riser: deckRiser,
+}
+const LABWARE_LOAD_NAMES_TO_SHOW_IMAGE: { [key: string]: string } = {
+ opentrons_tough_pcr_auto_sealing_lid: tcLid,
+}
interface LabwareStackModalProps {
labwareIdTop: string
commands: RunTimeCommand[] | null
@@ -87,6 +96,7 @@ export const LabwareStackModal = (
moduleModel,
labwareName,
labwareNickname,
+ labwareQuantity,
} = getLocationInfoNames(labwareIdTop, commands)
const topDefinition = getSlotLabwareDefinition(labwareIdTop, commands)
@@ -106,7 +116,25 @@ export const LabwareStackModal = (
moduleModel != null ? getModuleDisplayName(moduleModel) : null ?? ''
const isAdapterForTiprack =
adapterDef?.parameters.loadName === 'opentrons_flex_96_tiprack_adapter'
- const tiprackAdapterImg =
+
+ const labwareImg =
+ topDefinition.parameters.loadName in LABWARE_LOAD_NAMES_TO_SHOW_IMAGE ? (
+
+ ) : null
+
+ const adapterImg =
+ adapterDef != null &&
+ adapterDef.parameters.loadName in ADAPTER_LOAD_NAMES_TO_SHOW_IMAGE ? (
+
+ ) : null
const moduleImg =
moduleModel != null ? (
@@ -139,25 +167,33 @@ export const LabwareStackModal = (
1
+ ? t('labware_quantity', { quantity: labwareQuantity })
+ : labwareNickname
+ }
/>
-
-
-
+ {labwareImg != null ? (
+ {labwareImg}
+ ) : (
+
+
+
+ )}
-
>
{adapterDef != null ? (
<>
+
- {isAdapterForTiprack ? (
- {tiprackAdapterImg}
+ {adapterImg != null ? (
+ {adapterImg}
) : (
)}
- {moduleModel != null ? (
-
- ) : null}
>
) : null}
{moduleModel != null ? (
-
-
- {moduleImg}
-
+ <>
+
+
+
+ {moduleImg}
+
+ >
) : null}
@@ -200,24 +236,35 @@ export const LabwareStackModal = (
<>
-
-
-
-
+ 1
+ ? t('labware_quantity', { quantity: labwareQuantity })
+ : labwareNickname
+ }
+ />
+ {labwareImg != null ? (
+ {labwareImg}
+ ) : (
+
+
+
+ )}
-
>
{adapterDef != null ? (
<>
+
- {isAdapterForTiprack ? (
- {tiprackAdapterImg}
+ {adapterImg != null ? (
+ {adapterImg}
) : (
)}
- {moduleModel != null ? (
-
- ) : null}
>
) : null}
{moduleModel != null ? (
-
-
- {moduleImg}
-
+ <>
+
+
+
+ {moduleImg}
+
+ >
) : null}
@@ -253,31 +300,23 @@ interface LabwareStackLabelProps {
}
function LabwareStackLabel(props: LabwareStackLabelProps): JSX.Element {
const { text, subText, isOnDevice = false } = props
- return isOnDevice ? (
-
- {text}
- {subText != null ? (
-
- {subText}
-
- ) : null}
-
- ) : (
+ return (
- {text}
+
+ {text}
+
{subText != null ? (
-
+
{subText}
) : null}
diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx
index 6269be78e83..f31a3bcf28d 100644
--- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx
+++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx
@@ -26,7 +26,7 @@ import {
} from '@opentrons/components'
import { useCreateLiveCommandMutation } from '@opentrons/react-api-client'
import {
- getLabwareDisplayName,
+ getTopLabwareInfo,
getModuleDisplayName,
getModuleType,
HEATERSHAKER_MODULE_TYPE,
@@ -37,6 +37,7 @@ import {
THERMOCYCLER_MODULE_V2,
} from '@opentrons/shared-data'
+import { getLocationInfoNames } from '/app/transformations/commands'
import { ToggleButton } from '/app/atoms/buttons'
import { Divider } from '/app/atoms/structure'
import { SecureLabwareModal } from './SecureLabwareModal'
@@ -47,14 +48,10 @@ import type {
RunTimeCommand,
ModuleType,
LabwareDefinition2,
- LoadModuleRunTimeCommand,
LoadLabwareRunTimeCommand,
} from '@opentrons/shared-data'
import type { ModuleRenderInfoForProtocol } from '/app/resources/runs'
-import type {
- LabwareSetupItem,
- NestedLabwareInfo,
-} from '/app/transformations/commands'
+import type { LabwareSetupItem } from '/app/transformations/commands'
import type { ModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention'
const LabwareRow = styled.div`
@@ -73,7 +70,6 @@ interface LabwareListItemProps extends LabwareSetupItem {
extraAttentionModules: ModuleTypesThatRequireExtraAttention[]
isFlex: boolean
commands: RunTimeCommand[]
- nestedLabwareInfo: NestedLabwareInfo | null
showLabwareSVG?: boolean
}
@@ -82,37 +78,48 @@ export function LabwareListItem(
): JSX.Element | null {
const {
attachedModuleInfo,
- nickName,
+ nickName: bottomLabwareNickname,
initialLocation,
- definition,
moduleModel,
- moduleLocation,
extraAttentionModules,
isFlex,
commands,
- nestedLabwareInfo,
showLabwareSVG,
+ labwareId: bottomLabwareId,
} = props
+ const loadLabwareCommands = commands?.filter(
+ (command): command is LoadLabwareRunTimeCommand =>
+ command.commandType === 'loadLabware'
+ )
+
+ const { topLabwareId, topLabwareDefinition } = getTopLabwareInfo(
+ bottomLabwareId ?? '',
+ loadLabwareCommands
+ )
+ const {
+ slotName,
+ labwareName,
+ labwareNickname,
+ labwareQuantity,
+ adapterName: bottomLabwareName,
+ } = getLocationInfoNames(topLabwareId, commands)
+
+ const isStacked =
+ labwareQuantity > 1 ||
+ bottomLabwareId !== topLabwareId ||
+ moduleModel != null
+
const { i18n, t } = useTranslation('protocol_setup')
const [
secureLabwareModalType,
setSecureLabwareModalType,
] = useState(null)
- const labwareDisplayName = getLabwareDisplayName(definition)
const { createLiveCommand } = useCreateLiveCommandMutation()
const [isLatchLoading, setIsLatchLoading] = useState(false)
const [isLatchClosed, setIsLatchClosed] = useState(false)
- let slotInfo: string | null = null
-
- if (initialLocation !== 'offDeck' && 'slotName' in initialLocation) {
- slotInfo = initialLocation.slotName
- } else if (
- initialLocation !== 'offDeck' &&
- 'addressableAreaName' in initialLocation
- ) {
- slotInfo = initialLocation.addressableAreaName
- } else if (initialLocation === 'offDeck') {
+ let slotInfo: string | null = slotName
+ if (initialLocation === 'offDeck') {
slotInfo = i18n.format(t('off_deck'), 'upperCase')
}
@@ -126,50 +133,20 @@ export function LabwareListItem(
| HeaterShakerOpenLatchCreateCommand
| HeaterShakerCloseLatchCreateCommand
- if (initialLocation !== 'offDeck' && 'labwareId' in initialLocation) {
- const loadedAdapter = commands.find(
- (command): command is LoadLabwareRunTimeCommand =>
- command.commandType === 'loadLabware' &&
- command.result?.labwareId === initialLocation.labwareId
- )
- const loadedAdapterLocation = loadedAdapter?.params.location
-
- if (loadedAdapterLocation != null && loadedAdapterLocation !== 'offDeck') {
- if ('slotName' in loadedAdapterLocation) {
- slotInfo = loadedAdapterLocation.slotName
- } else if ('moduleId' in loadedAdapterLocation) {
- const module = commands.find(
- (command): command is LoadModuleRunTimeCommand =>
- command.commandType === 'loadModule' &&
- command.result?.moduleId === loadedAdapterLocation.moduleId
- )
- if (module != null) {
- slotInfo = module.params.location.slotName
- moduleDisplayName = getModuleDisplayName(module.params.model)
- }
- }
- }
- }
- if (
- initialLocation !== 'offDeck' &&
- 'moduleId' in initialLocation &&
- moduleLocation != null &&
- moduleModel != null
- ) {
- const moduleName = getModuleDisplayName(moduleModel)
+ if (moduleModel != null) {
moduleType = getModuleType(moduleModel)
+ moduleDisplayName = getModuleDisplayName(moduleModel)
+
const moduleTypeNeedsAttention = extraAttentionModules.find(
extraAttentionModType => extraAttentionModType === moduleType
)
- let moduleSlotName = moduleLocation.slotName
- if (moduleType === THERMOCYCLER_MODULE_TYPE) {
- moduleSlotName = isFlex ? TC_MODULE_LOCATION_OT3 : TC_MODULE_LOCATION_OT2
- }
- slotInfo = moduleSlotName
- moduleDisplayName = moduleName
+
switch (moduleTypeNeedsAttention) {
case MAGNETIC_MODULE_TYPE:
case THERMOCYCLER_MODULE_TYPE:
+ if (moduleType === THERMOCYCLER_MODULE_TYPE) {
+ slotInfo = isFlex ? TC_MODULE_LOCATION_OT3 : TC_MODULE_LOCATION_OT2
+ }
if (moduleModel !== THERMOCYCLER_MODULE_V2) {
secureLabwareInstructions = (
)}
- {nestedLabwareInfo != null || moduleDisplayName != null ? (
-
- ) : null}
+ {isStacked ? : null}
- {nestedLabwareInfo != null &&
- nestedLabwareInfo?.sharedSlotId === slotInfo ? (
- <>
-
-
+
+ {showLabwareSVG && topLabwareDefinition != null ? (
+
+ ) : null}
+
+
+ {labwareName}
+
+
-
- {nestedLabwareInfo.nestedLabwareDisplayName}
-
-
- {nestedLabwareInfo.nestedLabwareNickName}
-
-
+ {labwareQuantity > 1
+ ? t('labware_quantity', { quantity: labwareQuantity })
+ : labwareNickname}
+
+
+ >
+ {bottomLabwareName != null ? (
+ <>
+
+
+ {bottomLabwareName}
+
+
+ {bottomLabwareNickname}
+
+
>
) : null}
-
- {showLabwareSVG ? (
-
- ) : null}
-
-
- {labwareDisplayName}
-
-
- {nickName}
-
-
-
{moduleDisplayName != null ? (
<>
@@ -371,9 +352,7 @@ export function LabwareListItem(
marginTop="3px"
>
))}
diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx
index 647f1543677..b71c84da0f8 100644
--- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx
+++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx
@@ -6,10 +6,7 @@ import {
StyledText,
COLORS,
} from '@opentrons/components'
-import {
- getLabwareSetupItemGroups,
- getNestedLabwareInfo,
-} from '/app/transformations/commands'
+import { getLabwareSetupItemGroups } from '/app/transformations/commands'
import { LabwareListItem } from './LabwareListItem'
import type { RunTimeCommand } from '@opentrons/shared-data'
@@ -56,6 +53,7 @@ export function SetupLabwareList(
{allItems.map((labwareItem, index) => {
+ // filtering out all labware that aren't on a module or the deck
const labwareOnAdapter = allItems.find(
item =>
labwareItem.initialLocation !== 'offDeck' &&
@@ -70,7 +68,6 @@ export function SetupLabwareList(
extraAttentionModules={extraAttentionModules}
{...labwareItem}
isFlex={isFlex}
- nestedLabwareInfo={getNestedLabwareInfo(labwareItem, commands)}
/>
)
})}
diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx
index 0334496fd6b..c8bc460bbf4 100644
--- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx
+++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx
@@ -12,7 +12,7 @@ import {
FLEX_ROBOT_TYPE,
getDeckDefFromRobotType,
getSimplestDeckConfigForProtocol,
- parseInitialLoadedLabwareByAdapter,
+ getTopLabwareInfo,
THERMOCYCLER_MODULE_V1,
} from '@opentrons/shared-data'
@@ -22,14 +22,17 @@ import {
getProtocolModulesInfo,
getLabwareRenderInfo,
} from '/app/transformations/analysis'
+import { LabwareStackModal } from '/app/molecules/LabwareStackModal'
import { getStandardDeckViewLayerBlockList } from '/app/local-resources/deck_configuration'
import { OffDeckLabwareList } from './OffDeckLabwareList'
+import type { LabwareOnDeck } from '@opentrons/components'
import type {
CompletedProtocolAnalysis,
ProtocolAnalysisOutput,
+ LoadLabwareRunTimeCommand,
+ RunTimeCommand,
} from '@opentrons/shared-data'
-import { LabwareStackModal } from '/app/molecules/LabwareStackModal'
interface SetupLabwareMapProps {
runId: string
@@ -49,31 +52,25 @@ export function SetupLabwareMap({
if (protocolAnalysis == null) return null
- const commands = protocolAnalysis.commands
+ const commands: RunTimeCommand[] = protocolAnalysis.commands
+ const loadLabwareCommands = commands?.filter(
+ (command): command is LoadLabwareRunTimeCommand =>
+ command.commandType === 'loadLabware'
+ )
const robotType = protocolAnalysis.robotType ?? FLEX_ROBOT_TYPE
const deckDef = getDeckDefFromRobotType(robotType)
const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef)
- const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter(
- commands
- )
-
const modulesOnDeck = protocolModulesInfo.map(module => {
- const labwareInAdapterInMod =
- module.nestedLabwareId != null
- ? initialLoadedLabwareByAdapter[module.nestedLabwareId]
- : null
- // only rendering the labware on top most layer so
- // either the adapter or the labware are rendered but not both
- const topLabwareDefinition =
- labwareInAdapterInMod?.result?.definition ?? module.nestedLabwareDef
- const topLabwareId =
- labwareInAdapterInMod?.result?.labwareId ?? module.nestedLabwareId
- const topLabwareDisplayName =
- labwareInAdapterInMod?.params.displayName ??
- module.nestedLabwareDisplayName
+ const isLabwareStacked =
+ module.nestedLabwareId != null && module.nestedLabwareDef != null
+ const {
+ topLabwareId,
+ topLabwareDefinition,
+ topLabwareDisplayName,
+ } = getTopLabwareInfo(module.nestedLabwareId ?? '', loadLabwareCommands)
return {
moduleModel: module.moduleDef.model,
@@ -84,15 +81,9 @@ export function SetupLabwareMap({
: {},
nestedLabwareDef: topLabwareDefinition,
- highlightLabware:
- topLabwareDefinition != null &&
- topLabwareId != null &&
- hoverLabwareId === topLabwareId,
- highlightShadowLabware:
- topLabwareDefinition != null &&
- topLabwareId != null &&
- hoverLabwareId === topLabwareId,
- stacked: topLabwareDefinition != null && topLabwareId != null,
+ highlightLabware: hoverLabwareId === topLabwareId,
+ highlightShadowLabware: hoverLabwareId === topLabwareId,
+ stacked: isLabwareStacked,
moduleChildren: (
// open modal
) : null}
@@ -130,59 +121,59 @@ export function SetupLabwareMap({
const labwareRenderInfo = getLabwareRenderInfo(protocolAnalysis, deckDef)
- const labwareOnDeck = map(
+ const labwareOnDeck: Array = map(
labwareRenderInfo,
- ({ labwareDef, displayName, slotName }, labwareId) => {
- const labwareInAdapter = initialLoadedLabwareByAdapter[labwareId]
- // only rendering the labware on top most layer so
- // either the adapter or the labware are rendered but not both
- const topLabwareDefinition =
- labwareInAdapter?.result?.definition ?? labwareDef
- const topLabwareId = labwareInAdapter?.result?.labwareId ?? labwareId
- const topLabwareDisplayName =
- labwareInAdapter?.params.displayName ?? displayName
- const isLabwareInStack =
- topLabwareDefinition != null &&
- topLabwareId != null &&
- labwareInAdapter != null
-
- return {
- labwareLocation: { slotName },
- definition: topLabwareDefinition,
+ ({ slotName }, labwareId) => {
+ const {
topLabwareId,
+ topLabwareDefinition,
topLabwareDisplayName,
- highlight: isLabwareInStack && hoverLabwareId === topLabwareId,
- highlightShadow: isLabwareInStack && hoverLabwareId === topLabwareId,
- labwareChildren: (
- {
- if (isLabwareInStack) {
- setLabwareStackDetailsLabwareId(topLabwareId)
- }
- }}
- onMouseEnter={() => {
- if (topLabwareDefinition != null && topLabwareId != null) {
- setHoverLabwareId(() => topLabwareId)
- }
- }}
- onMouseLeave={() => {
- setHoverLabwareId(null)
- }}
- >
-
-
- ),
- stacked: isLabwareInStack,
- }
+ } = getTopLabwareInfo(labwareId, loadLabwareCommands)
+ const isLabwareInStack = labwareId !== topLabwareId
+ return topLabwareDefinition != null
+ ? {
+ labwareLocation: { slotName },
+ definition: topLabwareDefinition,
+ highlight: isLabwareInStack && hoverLabwareId === topLabwareId,
+ highlightShadow:
+ isLabwareInStack && hoverLabwareId === topLabwareId,
+ stacked: isLabwareInStack,
+ labwareChildren: (
+ {
+ if (isLabwareInStack) {
+ setLabwareStackDetailsLabwareId(topLabwareId)
+ }
+ }}
+ onMouseEnter={() => {
+ if (topLabwareDefinition != null && topLabwareId != null) {
+ setHoverLabwareId(() => topLabwareId)
+ }
+ }}
+ onMouseLeave={() => {
+ setHoverLabwareId(null)
+ }}
+ >
+ {topLabwareDefinition != null ? (
+
+ ) : null}
+
+ ),
+ }
+ : null
}
)
+ const labwareOnDeckFiltered: LabwareOnDeck[] = labwareOnDeck.filter(
+ (labware): labware is LabwareOnDeck => labware != null
+ )
+
return (
@@ -191,7 +182,7 @@ export function SetupLabwareMap({
deckConfig={deckConfig}
deckLayerBlocklist={getStandardDeckViewLayerBlockList(robotType)}
robotType={robotType}
- labwareOnDeck={labwareOnDeck}
+ labwareOnDeck={labwareOnDeckFiltered}
modulesOnDeck={modulesOnDeck}
/>
diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx
index 904395f7c98..50afda3d92f 100644
--- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx
+++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx
@@ -3,7 +3,10 @@ import { fireEvent, screen } from '@testing-library/react'
import { describe, it, beforeEach, vi, expect } from 'vitest'
import { MemoryRouter } from 'react-router-dom'
-import { opentrons96PcrAdapterV1 } from '@opentrons/shared-data'
+import {
+ opentrons96PcrAdapterV1,
+ getTopLabwareInfo,
+} from '@opentrons/shared-data'
import { useCreateLiveCommandMutation } from '@opentrons/react-api-client'
import { renderWithProviders } from '/app/__testing-utils__'
@@ -14,6 +17,7 @@ import {
mockTemperatureModule,
mockThermocycler,
} from '/app/redux/modules/__fixtures__'
+import { getLocationInfoNames } from '/app/transformations/commands'
import { mockLabwareDef } from '/app/organisms/LabwarePositionCheck/__fixtures__/mockLabwareDef'
import { SecureLabwareModal } from '../SecureLabwareModal'
import { LabwareListItem } from '../LabwareListItem'
@@ -28,7 +32,15 @@ import type { AttachedModule } from '/app/redux/modules/types'
import type { ModuleRenderInfoForProtocol } from '/app/resources/runs'
vi.mock('../SecureLabwareModal')
+vi.mock('/app/transformations/commands')
vi.mock('@opentrons/react-api-client')
+vi.mock('@opentrons/shared-data', async importOriginal => {
+ const actualSharedData = await importOriginal()
+ return {
+ ...actualSharedData,
+ getTopLabwareInfo: vi.fn(),
+ }
+})
const mockAdapterDef = opentrons96PcrAdapterV1 as LabwareDefinition2
const mockAdapterId = 'mockAdapterId'
@@ -87,12 +99,23 @@ describe('LabwareListItem', () => {
vi.mocked(useCreateLiveCommandMutation).mockReturnValue({
createLiveCommand: mockCreateLiveCommand,
} as any)
+ vi.mocked(getLocationInfoNames).mockReturnValue({
+ slotName: '7',
+ labwareName: 'Mock Labware Definition',
+ labwareNickname: 'nickName',
+ labwareQuantity: 1,
+ })
+ vi.mocked(getTopLabwareInfo).mockReturnValue({
+ topLabwareId: '1',
+ topLabwareDefinition: mockLabwareDef,
+ })
})
it('renders the correct info for a thermocycler (OT2), clicking on secure labware instructions opens the modal', () => {
render({
commands: [],
nickName: mockNickName,
+ labwareId: '7',
definition: mockLabwareDef,
initialLocation: { moduleId: mockModuleId },
moduleModel: 'thermocyclerModuleV1' as ModuleModel,
@@ -107,7 +130,6 @@ describe('LabwareListItem', () => {
} as any) as ModuleRenderInfoForProtocol,
},
isFlex: false,
- nestedLabwareInfo: null,
})
screen.getByText('Mock Labware Definition')
screen.getByText('nickName')
@@ -137,7 +159,6 @@ describe('LabwareListItem', () => {
} as any) as ModuleRenderInfoForProtocol,
},
isFlex: true,
- nestedLabwareInfo: null,
})
screen.getByText('Mock Labware Definition')
screen.getByText('A1+B1')
@@ -168,7 +189,6 @@ describe('LabwareListItem', () => {
} as any) as ModuleRenderInfoForProtocol,
},
isFlex: false,
- nestedLabwareInfo: null,
})
screen.getByText('Mock Labware Definition')
screen.getByTestId('slot_info_7')
@@ -203,7 +223,6 @@ describe('LabwareListItem', () => {
} as any) as ModuleRenderInfoForProtocol,
},
isFlex: false,
- nestedLabwareInfo: null,
})
screen.getByText('Mock Labware Definition')
screen.getByTestId('slot_info_7')
@@ -245,7 +264,7 @@ describe('LabwareListItem', () => {
nickName: mockNickName,
definition: mockLabwareDef,
initialLocation: { labwareId: mockAdapterId },
- moduleModel: 'temperatureModuleV1' as ModuleModel,
+ moduleModel: 'temperatureModuleV2' as ModuleModel,
moduleLocation: mockModuleSlot,
extraAttentionModules: [],
attachedModuleInfo: {
@@ -262,18 +281,11 @@ describe('LabwareListItem', () => {
} as any) as ModuleRenderInfoForProtocol,
},
isFlex: false,
- nestedLabwareInfo: {
- nestedLabwareDisplayName: 'mock nested display name',
- sharedSlotId: '7',
- nestedLabwareNickName: 'nestedLabwareNickName',
- nestedLabwareDefinition: mockLabwareDef,
- },
})
screen.getByText('Mock Labware Definition')
screen.getAllByText('7')
screen.getByText('Temperature Module GEN2')
- screen.getByText('mock nested display name')
- screen.getByText('nestedLabwareNickName')
+ screen.getByText('Mock Labware Definition')
screen.getByText('nickName')
})
@@ -293,10 +305,17 @@ describe('LabwareListItem', () => {
z: 1.2,
},
} as any
+ vi.mocked(getLocationInfoNames).mockReturnValue({
+ slotName: 'A2',
+ labwareName: 'Mock Labware Name',
+ labwareNickname: 'labware nick name',
+ labwareQuantity: 1,
+ adapterName: 'mock adapter name',
+ })
render({
commands: [mockAdapterLoadCommand],
- nickName: mockNickName,
+ nickName: 'mock adapter nick name',
definition: mockLabwareDef,
initialLocation: { labwareId: mockAdapterId },
moduleModel: null,
@@ -304,18 +323,13 @@ describe('LabwareListItem', () => {
extraAttentionModules: [],
attachedModuleInfo: {},
isFlex: false,
- nestedLabwareInfo: {
- nestedLabwareDisplayName: 'mock nested display name',
- sharedSlotId: 'A2',
- nestedLabwareNickName: 'nestedLabwareNickName',
- nestedLabwareDefinition: mockLabwareDef,
- },
+ labwareId: '5',
})
- screen.getByText('Mock Labware Definition')
+ screen.getByText('Mock Labware Name')
+ screen.getByText('labware nick name')
screen.getByText('A2')
- screen.getByText('mock nested display name')
- screen.getByText('nestedLabwareNickName')
- screen.getByText('nickName')
+ screen.getByText('mock adapter name')
+ screen.getByText('mock adapter nick name')
})
it('renders the correct info for a labware on top of a heater shaker', () => {
@@ -341,7 +355,6 @@ describe('LabwareListItem', () => {
} as any) as ModuleRenderInfoForProtocol,
},
isFlex: false,
- nestedLabwareInfo: null,
})
screen.getByText('Mock Labware Definition')
screen.getByTestId('slot_info_7')
@@ -363,6 +376,7 @@ describe('LabwareListItem', () => {
})
it('renders the correct info for an off deck labware', () => {
+ vi.mocked(getTopLabwareInfo)
render({
nickName: null,
definition: mockLabwareDef,
@@ -373,7 +387,6 @@ describe('LabwareListItem', () => {
extraAttentionModules: [],
attachedModuleInfo: {},
isFlex: false,
- nestedLabwareInfo: null,
})
screen.getByText('Mock Labware Definition')
screen.getByTestId('slot_info_OFF DECK')
diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx
index 5338a9ce055..1b556692f8d 100644
--- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx
+++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx
@@ -14,7 +14,7 @@ import {
FLEX_ROBOT_TYPE,
getDeckDefFromRobotType,
getSimplestDeckConfigForProtocol,
- parseInitialLoadedLabwareByAdapter,
+ getTopLabwareInfo,
parseLabwareInfoByLiquidId,
parseLiquidsInLoadOrder,
THERMOCYCLER_MODULE_V1,
@@ -32,6 +32,8 @@ import {
import type {
CompletedProtocolAnalysis,
ProtocolAnalysisOutput,
+ RunTimeCommand,
+ LoadLabwareRunTimeCommand,
} from '@opentrons/shared-data'
interface SetupLiquidsMapProps {
@@ -50,13 +52,16 @@ export function SetupLiquidsMap(
if (protocolAnalysis == null) return null
+ const commands: RunTimeCommand[] = protocolAnalysis.commands
+ const loadLabwareCommands = commands?.filter(
+ (command): command is LoadLabwareRunTimeCommand =>
+ command.commandType === 'loadLabware'
+ )
+
const liquids = parseLiquidsInLoadOrder(
protocolAnalysis.liquids != null ? protocolAnalysis.liquids : [],
protocolAnalysis.commands ?? []
)
- const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter(
- protocolAnalysis.commands ?? []
- )
const robotType = protocolAnalysis.robotType ?? FLEX_ROBOT_TYPE
const deckDef = getDeckDefFromRobotType(robotType)
const labwareRenderInfo = getLabwareRenderInfo(protocolAnalysis, deckDef)
@@ -69,19 +74,11 @@ export function SetupLiquidsMap(
const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef)
const modulesOnDeck = protocolModulesInfo.map(module => {
- const labwareInAdapterInMod =
- module.nestedLabwareId != null
- ? initialLoadedLabwareByAdapter[module.nestedLabwareId]
- : null
- // only rendering the labware on top most layer so
- // either the adapter or the labware are rendered but not both
- const topLabwareDefinition =
- labwareInAdapterInMod?.result?.definition ?? module.nestedLabwareDef
- const topLabwareId =
- labwareInAdapterInMod?.result?.labwareId ?? module.nestedLabwareId
- const topLabwareDisplayName =
- labwareInAdapterInMod?.params.displayName ??
- module.nestedLabwareDisplayName
+ const {
+ topLabwareId,
+ topLabwareDefinition,
+ topLabwareDisplayName,
+ } = getTopLabwareInfo(module.nestedLabwareId ?? '', loadLabwareCommands)
const nestedLabwareWellFill = getWellFillFromLabwareId(
topLabwareId ?? '',
liquids,
@@ -120,7 +117,7 @@ export function SetupLiquidsMap(
hover={topLabwareId === hoverLabwareId && labwareHasLiquid}
labwareHasLiquid={labwareHasLiquid}
labwareId={topLabwareId}
- displayName={topLabwareDisplayName}
+ displayName={topLabwareDisplayName ?? null}
runId={runId}
/>
@@ -140,59 +137,52 @@ export function SetupLiquidsMap(
labwareOnDeck={[]}
modulesOnDeck={modulesOnDeck}
>
- {map(
- labwareRenderInfo,
- ({ x, y, labwareDef, displayName }, labwareId) => {
- const labwareInAdapter = initialLoadedLabwareByAdapter[labwareId]
- // only rendering the labware on top most layer so
- // either the adapter or the labware are rendered but not both
- const topLabwareDefinition =
- labwareInAdapter?.result?.definition ?? labwareDef
- const topLabwareId =
- labwareInAdapter?.result?.labwareId ?? labwareId
- const topLabwareDisplayName =
- labwareInAdapter?.params.displayName ?? displayName
- const wellFill = getWellFillFromLabwareId(
- topLabwareId ?? '',
- liquids,
- labwareByLiquidId
- )
- const labwareHasLiquid = !isEmpty(wellFill)
- return (
-
- {
- setHoverLabwareId(topLabwareId)
- }}
- onMouseLeave={() => {
- setHoverLabwareId('')
- }}
- onClick={() => {
- if (labwareHasLiquid) {
- setLiquidDetailsLabwareId(topLabwareId)
- }
- }}
- cursor={labwareHasLiquid ? 'pointer' : ''}
- >
-
-
-
-
- )
- }
- )}
+ {map(labwareRenderInfo, ({ x, y }, labwareId) => {
+ const {
+ topLabwareId,
+ topLabwareDefinition,
+ topLabwareDisplayName,
+ } = getTopLabwareInfo(labwareId, loadLabwareCommands)
+ const wellFill = getWellFillFromLabwareId(
+ topLabwareId ?? '',
+ liquids,
+ labwareByLiquidId
+ )
+ const labwareHasLiquid = !isEmpty(wellFill)
+ return topLabwareDefinition != null ? (
+
+ {
+ setHoverLabwareId(topLabwareId)
+ }}
+ onMouseLeave={() => {
+ setHoverLabwareId('')
+ }}
+ onClick={() => {
+ if (labwareHasLiquid) {
+ setLiquidDetailsLabwareId(topLabwareId)
+ }
+ }}
+ cursor={labwareHasLiquid ? 'pointer' : ''}
+ >
+
+
+
+
+ ) : null
+ })}
{liquidDetailsLabwareId != null && (
{
vi.mocked(getLocationInfoNames).mockReturnValue({
labwareName: 'mock labware name',
slotName: '4',
+ labwareQuantity: 1,
})
mockTrackEvent = vi.fn()
vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent)
diff --git a/app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidsLabwareDetailsModal.test.tsx b/app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidsLabwareDetailsModal.test.tsx
index 54b8239da47..967a840ee75 100644
--- a/app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidsLabwareDetailsModal.test.tsx
+++ b/app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidsLabwareDetailsModal.test.tsx
@@ -65,6 +65,7 @@ describe('LiquidsLabwareDetailsModal', () => {
vi.mocked(getLocationInfoNames).mockReturnValue({
labwareName: 'mock labware name',
slotName: '5',
+ labwareQuantity: 1,
})
vi.mocked(getSlotLabwareDefinition).mockReturnValue(mockDefinition)
vi.mocked(getLiquidsByIdForLabware).mockReturnValue({
diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx
index 21b6fb20854..339ad981daa 100644
--- a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx
+++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx
@@ -3,18 +3,22 @@ import { BaseDeck, Flex } from '@opentrons/components'
import {
FLEX_ROBOT_TYPE,
getSimplestDeckConfigForProtocol,
+ getTopLabwareInfo,
THERMOCYCLER_MODULE_V1,
} from '@opentrons/shared-data'
import { getStandardDeckViewLayerBlockList } from '/app/local-resources/deck_configuration'
import { getLabwareRenderInfo } from '/app/transformations/analysis'
+import type { LabwareOnDeck } from '@opentrons/components'
import type {
CompletedProtocolAnalysis,
DeckDefinition,
LabwareDefinition2,
- LoadedLabwareByAdapter,
+ RunTimeCommand,
+ LoadLabwareRunTimeCommand,
} from '@opentrons/shared-data'
+
import type { AttachedProtocolModuleMatch } from '/app/transformations/analysis'
interface LabwareMapViewProps {
@@ -23,7 +27,6 @@ interface LabwareMapViewProps {
labwareDef: LabwareDefinition2,
labwareId: string
) => void
- initialLoadedLabwareByAdapter: LoadedLabwareByAdapter
deckDef: DeckDefinition
mostRecentAnalysis: CompletedProtocolAnalysis | null
}
@@ -32,11 +35,16 @@ export function LabwareMapView(props: LabwareMapViewProps): JSX.Element {
const {
handleLabwareClick,
attachedProtocolModuleMatches,
- initialLoadedLabwareByAdapter,
deckDef,
mostRecentAnalysis,
} = props
const deckConfig = getSimplestDeckConfigForProtocol(mostRecentAnalysis)
+ const commands: RunTimeCommand[] = mostRecentAnalysis?.commands ?? []
+ const loadLabwareCommands = commands?.filter(
+ (command): command is LoadLabwareRunTimeCommand =>
+ command.commandType === 'loadLabware'
+ )
+
const labwareRenderInfo =
mostRecentAnalysis != null
? getLabwareRenderInfo(mostRecentAnalysis, deckDef)
@@ -44,16 +52,11 @@ export function LabwareMapView(props: LabwareMapViewProps): JSX.Element {
const modulesOnDeck = attachedProtocolModuleMatches.map(module => {
const { moduleDef, nestedLabwareDef, nestedLabwareId, slotName } = module
- const labwareInAdapterInMod =
- nestedLabwareId != null
- ? initialLoadedLabwareByAdapter[nestedLabwareId]
- : null
- // only rendering the labware on top most layer so
- // either the adapter or the labware are rendered but not both
- const topLabwareDefinition =
- labwareInAdapterInMod?.result?.definition ?? nestedLabwareDef
- const topLabwareId =
- labwareInAdapterInMod?.result?.labwareId ?? nestedLabwareId
+ const isLabwareStacked = nestedLabwareId != null && nestedLabwareDef != null
+ const { topLabwareId, topLabwareDefinition } = getTopLabwareInfo(
+ module.nestedLabwareId ?? '',
+ loadLabwareCommands
+ )
return {
moduleModel: moduleDef.model,
@@ -70,49 +73,48 @@ export function LabwareMapView(props: LabwareMapViewProps): JSX.Element {
}
: undefined,
highlightLabware: true,
- highlightShadowLabware:
- topLabwareDefinition != null && topLabwareId != null,
+ highlightShadowLabware: isLabwareStacked,
moduleChildren: null,
- stacked: topLabwareDefinition != null && topLabwareId != null,
+ stacked: isLabwareStacked,
}
})
- const labwareLocations = map(
+ const labwareLocations: Array = map(
labwareRenderInfo,
- ({ labwareDef, slotName }, labwareId) => {
- const labwareInAdapter = initialLoadedLabwareByAdapter[labwareId]
- // only rendering the labware on top most layer so
- // either the adapter or the labware are rendered but not both
- const topLabwareDefinition =
- labwareInAdapter?.result?.definition ?? labwareDef
- const topLabwareId = labwareInAdapter?.result?.labwareId ?? labwareId
- const isLabwareInStack =
- topLabwareDefinition != null &&
- topLabwareId != null &&
- labwareInAdapter != null
+ ({ slotName }, labwareId) => {
+ const { topLabwareId, topLabwareDefinition } = getTopLabwareInfo(
+ labwareId,
+ loadLabwareCommands
+ )
+ const isLabwareInStack = labwareId !== topLabwareId
- return {
- labwareLocation: { slotName },
- definition: topLabwareDefinition,
- topLabwareId,
- onLabwareClick: () => {
- handleLabwareClick(topLabwareDefinition, topLabwareId)
- },
- labwareChildren: null,
- highlight: true,
- highlightShadow: isLabwareInStack,
- stacked: isLabwareInStack,
- }
+ return topLabwareDefinition != null
+ ? {
+ labwareLocation: { slotName },
+ definition: topLabwareDefinition,
+ onLabwareClick: () => {
+ handleLabwareClick(topLabwareDefinition, topLabwareId)
+ },
+ highlight: true,
+ highlightShadow: isLabwareInStack,
+ stacked: isLabwareInStack,
+ }
+ : null
}
)
+ const labwareLocationsFiltered: LabwareOnDeck[] = labwareLocations.filter(
+ (labwareLocation): labwareLocation is LabwareOnDeck =>
+ labwareLocation != null
+ )
+
return (
diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx
index 8729ae0f811..860d927578e 100644
--- a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx
+++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx
@@ -114,7 +114,6 @@ describe('LabwareMapView', () => {
handleLabwareClick: vi.fn(),
deckDef: (deckDefFixture as unknown) as DeckDefinition,
mostRecentAnalysis: ({} as unknown) as CompletedProtocolAnalysis,
- initialLoadedLabwareByAdapter: {},
attachedProtocolModuleMatches: [
{
...mockProtocolModuleInfo[0],
diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx
index 1a54e2fc00d..2d440fc9516 100644
--- a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx
+++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx
@@ -25,10 +25,11 @@ import {
FLEX_ROBOT_TYPE,
getDeckDefFromRobotType,
getLabwareDefURI,
- getLabwareDisplayName,
+ getTopLabwareInfo,
getModuleDisplayName,
HEATERSHAKER_MODULE_TYPE,
- parseInitialLoadedLabwareByAdapter,
+ TC_MODULE_LOCATION_OT3,
+ THERMOCYCLER_MODULE_TYPE,
} from '@opentrons/shared-data'
import {
useCreateLiveCommandMutation,
@@ -38,8 +39,8 @@ import {
import { FloatingActionButton, SmallButton } from '/app/atoms/buttons'
import { ODDBackButton } from '/app/molecules/ODDBackButton'
import {
+ getLocationInfoNames,
getLabwareSetupItemGroups,
- getNestedLabwareInfo,
} from '/app/transformations/commands'
import {
getAttachedProtocolModuleMatches,
@@ -56,15 +57,12 @@ import type {
HeaterShakerCloseLatchCreateCommand,
HeaterShakerOpenLatchCreateCommand,
LabwareDefinition2,
- LabwareLocation,
LoadLabwareRunTimeCommand,
+ LabwareLocation,
RunTimeCommand,
} from '@opentrons/shared-data'
import type { HeaterShakerModule, Modules } from '@opentrons/api-client'
-import type {
- LabwareSetupItem,
- NestedLabwareInfo,
-} from '/app/transformations/commands'
+import type { LabwareSetupItem } from '/app/transformations/commands'
import type { SetupScreens } from '../types'
import type { AttachedProtocolModuleMatch } from '/app/transformations/analysis'
@@ -121,9 +119,6 @@ export function ProtocolSetupLabware({
protocolModulesInfo,
deckConfig
)
- const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter(
- mostRecentAnalysis?.commands ?? []
- )
const handleLabwareClick = (
labwareDef: LabwareDefinition2,
@@ -152,7 +147,7 @@ export function ProtocolSetupLabware({
}
}
}
- const selectedLabwareIsTopOfStack = mostRecentAnalysis?.commands.some(
+ const selectedLabwareIsStacked = mostRecentAnalysis?.commands.some(
command =>
command.commandType === 'loadLabware' &&
command.result?.labwareId === selectedLabware?.id &&
@@ -164,7 +159,7 @@ export function ProtocolSetupLabware({
return (
<>
{showLabwareDetailsModal &&
- !selectedLabwareIsTopOfStack &&
+ !selectedLabwareIsStacked &&
selectedLabware != null ? (
) : (
<>
@@ -239,17 +233,14 @@ export function ProtocolSetupLabware({
'labwareId' in labware.initialLocation &&
item.labwareId === labware.initialLocation.labwareId
)
- return mostRecentAnalysis != null && labwareOnAdapter == null ? (
+ return mostRecentAnalysis?.commands != null &&
+ labwareOnAdapter == null ? (
) : null
})}
@@ -257,7 +248,7 @@ export function ProtocolSetupLabware({
)}
{showLabwareDetailsModal &&
selectedLabware != null &&
- selectedLabwareIsTopOfStack ? (
+ selectedLabwareIsStacked ? (
['refetch']
- nestedLabwareInfo: NestedLabwareInfo | null
- commands?: RunTimeCommand[]
+ commands: RunTimeCommand[]
}
function RowLabware({
labware,
attachedProtocolModules,
refetchModules,
- nestedLabwareInfo,
commands,
}: RowLabwareProps): JSX.Element | null {
- const { definition, initialLocation, nickName } = labware
+ const {
+ initialLocation,
+ nickName: bottomLabwareNickname,
+ labwareId: bottomLabwareId,
+ } = labware
+ const loadLabwareCommands = commands?.filter(
+ (command): command is LoadLabwareRunTimeCommand =>
+ command.commandType === 'loadLabware'
+ )
+
+ const { topLabwareId } = getTopLabwareInfo(
+ bottomLabwareId ?? '',
+ loadLabwareCommands
+ )
+ const {
+ slotName: slot,
+ labwareName: topLabwareName,
+ labwareNickname: topLabwareNickname,
+ labwareQuantity: topLabwareQuantity,
+ adapterName,
+ } = getLocationInfoNames(topLabwareId, commands)
+
const { t, i18n } = useTranslation([
'protocol_command_text',
'protocol_setup',
@@ -451,47 +461,21 @@ function RowLabware({
matchedModule.attachedModuleMatch.moduleType === HEATERSHAKER_MODULE_TYPE
? matchedModule.attachedModuleMatch
: null
+ const isStacked =
+ topLabwareQuantity > 1 || adapterName != null || matchedModule != null
- let slotName: string = ''
- let location: JSX.Element | string | null = null
+ let slotName: string = slot
+ let location: JSX.Element =
if (initialLocation === 'offDeck') {
location = (
)
- } else if ('slotName' in initialLocation) {
- slotName = initialLocation.slotName
- location =
- } else if ('addressableAreaName' in initialLocation) {
- slotName = initialLocation.addressableAreaName
- location =
- } else if (labware.moduleLocation != null) {
- location = (
- <>
-
- >
- )
- } else if ('labwareId' in initialLocation) {
- const adapterId = initialLocation.labwareId
- const adapterLocation = commands?.find(
- (command): command is LoadLabwareRunTimeCommand =>
- command.commandType === 'loadLabware' &&
- command.result?.labwareId === adapterId
- )?.params.location
-
- if (adapterLocation != null && adapterLocation !== 'offDeck') {
- if ('slotName' in adapterLocation) {
- slotName = adapterLocation.slotName
- location =
- } else if ('moduleId' in adapterLocation) {
- const moduleUnderAdapter = attachedProtocolModules.find(
- module => module.moduleId === adapterLocation.moduleId
- )
- if (moduleUnderAdapter != null) {
- slotName = moduleUnderAdapter.slotName
- location =
- }
- }
- }
+ } else if (
+ matchedModule != null &&
+ matchedModule.attachedModuleMatch?.moduleType === THERMOCYCLER_MODULE_TYPE
+ ) {
+ slotName = TC_MODULE_LOCATION_OT3
+ location =
}
return (
{location}
- {nestedLabwareInfo != null || matchedModule != null ? (
-
- ) : null}
+ {isStacked ? : null}
- {getLabwareDisplayName(definition)}
+ {topLabwareName}
- {nickName}
+ {topLabwareQuantity > 1
+ ? t('protocol_setup:labware_quantity', {
+ quantity: topLabwareQuantity,
+ })
+ : topLabwareNickname}
- {nestedLabwareInfo != null &&
- nestedLabwareInfo?.sharedSlotId === slotName ? (
+ {adapterName != null ? (
<>
- {nestedLabwareInfo.nestedLabwareDisplayName}
+ {adapterName}
- {nestedLabwareInfo.nestedLabwareNickName}
+ {bottomLabwareNickname}
>
diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx
index feeb3e863a4..720b6db7545 100644
--- a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx
+++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx
@@ -43,6 +43,7 @@ describe('LiquidDetails', () => {
vi.mocked(getLocationInfoNames).mockReturnValue({
slotName: '4',
labwareName: 'mock labware name',
+ labwareQuantity: 1,
})
vi.mocked(LiquidsLabwareDetailsModal).mockReturnValue(mock modal
)
})
diff --git a/app/src/transformations/commands/transformations/__tests__/getLocationInfoNames.test.ts b/app/src/transformations/commands/transformations/__tests__/getLocationInfoNames.test.ts
index d0b3551972f..f722caeb076 100644
--- a/app/src/transformations/commands/transformations/__tests__/getLocationInfoNames.test.ts
+++ b/app/src/transformations/commands/transformations/__tests__/getLocationInfoNames.test.ts
@@ -1,5 +1,8 @@
import { describe, it, vi, expect, beforeEach } from 'vitest'
-import { getLabwareDisplayName } from '@opentrons/shared-data'
+import {
+ getLabwareDisplayName,
+ getLabwareStackCountAndLocation,
+} from '@opentrons/shared-data'
import { getLocationInfoNames } from '../getLocationInfoNames'
import type { ModuleModel } from '@opentrons/shared-data'
@@ -154,11 +157,16 @@ vi.mock('@opentrons/shared-data')
describe('getLocationInfoNames', () => {
beforeEach(() => {
vi.mocked(getLabwareDisplayName).mockReturnValue(LABWARE_DISPLAY_NAME)
+ vi.mocked(getLabwareStackCountAndLocation).mockReturnValue({
+ labwareLocation: { slotName: SLOT },
+ labwareQuantity: 1,
+ })
})
it('returns labware name and slot number for labware id on the deck', () => {
const expected = {
slotName: SLOT,
labwareName: LABWARE_DISPLAY_NAME,
+ labwareQuantity: 1,
}
expect(
getLocationInfoNames(LABWARE_ID, MOCK_LOAD_LABWARE_COMMANDS as any)
@@ -169,7 +177,12 @@ describe('getLocationInfoNames', () => {
slotName: SLOT,
labwareName: LABWARE_DISPLAY_NAME,
moduleModel: MOCK_MODEL,
+ labwareQuantity: 1,
}
+ vi.mocked(getLabwareStackCountAndLocation).mockReturnValue({
+ labwareLocation: { moduleId: '12345' },
+ labwareQuantity: 1,
+ })
expect(getLocationInfoNames(LABWARE_ID, MOCK_MOD_COMMANDS as any)).toEqual(
expected
)
@@ -181,7 +194,12 @@ describe('getLocationInfoNames', () => {
moduleModel: MOCK_MODEL,
adapterName: ADAPTER_DISPLAY_NAME,
adapterId: ADAPTER_ID,
+ labwareQuantity: 1,
}
+ vi.mocked(getLabwareStackCountAndLocation).mockReturnValue({
+ labwareLocation: { labwareId: ADAPTER_ID },
+ labwareQuantity: 1,
+ })
expect(
getLocationInfoNames(LABWARE_ID, MOCK_ADAPTER_MOD_COMMANDS as any)
).toEqual(expected)
@@ -192,7 +210,12 @@ describe('getLocationInfoNames', () => {
labwareName: LABWARE_DISPLAY_NAME,
adapterName: ADAPTER_DISPLAY_NAME,
adapterId: ADAPTER_ID,
+ labwareQuantity: 1,
}
+ vi.mocked(getLabwareStackCountAndLocation).mockReturnValue({
+ labwareLocation: { labwareId: ADAPTER_ID },
+ labwareQuantity: 1,
+ })
expect(
getLocationInfoNames(LABWARE_ID, MOCK_ADAPTER_COMMANDS as any)
).toEqual(expected)
@@ -203,7 +226,12 @@ describe('getLocationInfoNames', () => {
labwareName: LABWARE_DISPLAY_NAME,
adapterName: ADAPTER_DISPLAY_NAME,
adapterId: ADAPTER_ID,
+ labwareQuantity: 1,
}
+ vi.mocked(getLabwareStackCountAndLocation).mockReturnValue({
+ labwareLocation: { labwareId: ADAPTER_ID },
+ labwareQuantity: 1,
+ })
expect(
getLocationInfoNames(LABWARE_ID, MOCK_ADAPTER_EXTENSION_COMMANDS as any)
).toEqual(expected)
diff --git a/app/src/transformations/commands/transformations/getLocationInfoNames.ts b/app/src/transformations/commands/transformations/getLocationInfoNames.ts
index 26d618859f9..e87f5d68ba8 100644
--- a/app/src/transformations/commands/transformations/getLocationInfoNames.ts
+++ b/app/src/transformations/commands/transformations/getLocationInfoNames.ts
@@ -1,4 +1,8 @@
-import { getLabwareDisplayName } from '@opentrons/shared-data'
+import {
+ getLabwareDisplayName,
+ getLabwareStackCountAndLocation,
+} from '@opentrons/shared-data'
+
import type {
LoadLabwareRunTimeCommand,
RunTimeCommand,
@@ -10,6 +14,7 @@ export interface LocationInfoNames {
slotName: string
labwareName: string
labwareNickname?: string
+ labwareQuantity: number
adapterName?: string
moduleModel?: ModuleModel
adapterId?: string
@@ -30,11 +35,11 @@ export function getLocationInfoNames(
(command): command is LoadModuleRunTimeCommand =>
command.commandType === 'loadModule'
)
- if (loadLabwareCommand == null) {
+ if (loadLabwareCommands == null || loadLabwareCommand == null) {
console.warn(
`could not find the load labware command assosciated with thie labwareId: ${labwareId}`
)
- return { slotName: '', labwareName: '' }
+ return { slotName: '', labwareName: '', labwareQuantity: 0 }
}
const labwareName =
@@ -43,14 +48,21 @@ export function getLocationInfoNames(
: ''
const labwareNickname = loadLabwareCommand.params.displayName
- const labwareLocation = loadLabwareCommand.params.location
+ const { labwareLocation, labwareQuantity } = getLabwareStackCountAndLocation(
+ labwareId,
+ loadLabwareCommands
+ )
if (labwareLocation === 'offDeck') {
- return { slotName: 'Off deck', labwareName }
+ return { slotName: 'Off deck', labwareName, labwareQuantity }
} else if ('slotName' in labwareLocation) {
- return { slotName: labwareLocation.slotName, labwareName }
+ return { slotName: labwareLocation.slotName, labwareName, labwareQuantity }
} else if ('addressableAreaName' in labwareLocation) {
- return { slotName: labwareLocation.addressableAreaName, labwareName }
+ return {
+ slotName: labwareLocation.addressableAreaName,
+ labwareName,
+ labwareQuantity,
+ }
} else if ('moduleId' in labwareLocation) {
const loadModuleCommandUnderLabware = loadModuleCommands?.find(
command => command.result?.moduleId === labwareLocation.moduleId
@@ -62,9 +74,11 @@ export function getLocationInfoNames(
loadModuleCommandUnderLabware?.params.location.slotName ?? '',
labwareName,
moduleModel: loadModuleCommandUnderLabware?.params.model,
+ labwareQuantity,
}
- : { slotName: '', labwareName: '' }
+ : { slotName: '', labwareName: '', labwareQuantity }
} else {
+ // adapt this to return the adapter only if the role of this labware is adapter -- otherwise, keep parsing through until you find out how many identical labware there are
const loadedAdapterCommand = loadLabwareCommands?.find(command =>
command.result != null
? command.result?.labwareId === labwareLocation.labwareId
@@ -74,7 +88,7 @@ export function getLocationInfoNames(
console.warn(
`expected to find an adapter under the labware but could not with labwareId ${labwareLocation.labwareId}`
)
- return { slotName: '', labwareName: labwareName }
+ return { slotName: '', labwareName: labwareName, labwareQuantity }
} else if (
loadedAdapterCommand?.params.location !== 'offDeck' &&
'slotName' in loadedAdapterCommand?.params.location
@@ -86,6 +100,7 @@ export function getLocationInfoNames(
adapterName:
loadedAdapterCommand?.result?.definition.metadata.displayName,
adapterId: loadedAdapterCommand?.result?.labwareId,
+ labwareQuantity,
}
} else if (
loadedAdapterCommand?.params.location !== 'offDeck' &&
@@ -98,6 +113,7 @@ export function getLocationInfoNames(
adapterName:
loadedAdapterCommand?.result?.definition.metadata.displayName,
adapterId: loadedAdapterCommand?.result?.labwareId,
+ labwareQuantity,
}
} else if (
loadedAdapterCommand?.params.location !== 'offDeck' &&
@@ -118,11 +134,12 @@ export function getLocationInfoNames(
loadedAdapterCommand.result?.definition.metadata.displayName,
adapterId: loadedAdapterCommand?.result?.labwareId,
moduleModel: loadModuleCommandUnderAdapter.params.model,
+ labwareQuantity,
}
- : { slotName: '', labwareName }
+ : { slotName: '', labwareName, labwareQuantity }
} else {
// shouldn't hit this
- return { slotName: '', labwareName }
+ return { slotName: '', labwareName, labwareQuantity }
}
}
}
diff --git a/components/src/hardware-sim/Labware/LabwareAdapter/OpentronsAutoclavableDeckRiser.tsx b/components/src/hardware-sim/Labware/LabwareAdapter/OpentronsAutoclavableDeckRiser.tsx
new file mode 100644
index 00000000000..d6685e0793c
--- /dev/null
+++ b/components/src/hardware-sim/Labware/LabwareAdapter/OpentronsAutoclavableDeckRiser.tsx
@@ -0,0 +1,100 @@
+// x .32, y .31
+export function OpentronsAutoclavableDeckRiser(): JSX.Element {
+ return (
+
+ )
+}
diff --git a/components/src/hardware-sim/Labware/LabwareAdapter/OpentronsToughPCRAutoSealingLid.tsx b/components/src/hardware-sim/Labware/LabwareAdapter/OpentronsToughPCRAutoSealingLid.tsx
new file mode 100644
index 00000000000..b3f50f94dd0
--- /dev/null
+++ b/components/src/hardware-sim/Labware/LabwareAdapter/OpentronsToughPCRAutoSealingLid.tsx
@@ -0,0 +1,83 @@
+export function OpentronsToughPCRAutoSealingLid(): JSX.Element {
+ return (
+
+ )
+}
diff --git a/components/src/hardware-sim/Labware/LabwareAdapter/index.tsx b/components/src/hardware-sim/Labware/LabwareAdapter/index.tsx
index f3941d980af..417b83ce89c 100644
--- a/components/src/hardware-sim/Labware/LabwareAdapter/index.tsx
+++ b/components/src/hardware-sim/Labware/LabwareAdapter/index.tsx
@@ -3,6 +3,8 @@ import { Opentrons96FlatBottomAdapter } from './Opentrons96FlatBottomAdapter'
import { OpentronsUniversalFlatAdapter } from './OpentronsUniversalFlatAdapter'
import { OpentronsAluminumFlatBottomPlate } from './OpentronsAluminumFlatBottomPlate'
import { OpentronsFlex96TiprackAdapter } from './OpentronsFlex96TiprackAdapter'
+import { OpentronsToughPCRAutoSealingLid } from './OpentronsToughPCRAutoSealingLid'
+import { OpentronsAutoclavableDeckRiser } from './OpentronsAutoclavableDeckRiser'
import { COLORS } from '../../../helix-design-system'
import { LabwareOutline } from '../labwareInternals'
import type { LabwareDefinition2 } from '@opentrons/shared-data'
@@ -13,6 +15,8 @@ const LABWARE_ADAPTER_LOADNAME_PATHS = {
opentrons_aluminum_flat_bottom_plate: OpentronsAluminumFlatBottomPlate,
opentrons_flex_96_tiprack_adapter: OpentronsFlex96TiprackAdapter,
opentrons_universal_flat_adapter: OpentronsUniversalFlatAdapter,
+ opentrons_tough_pcr_auto_sealing_lid: OpentronsToughPCRAutoSealingLid,
+ opentrons_flex_deck_riser: OpentronsAutoclavableDeckRiser,
}
export type LabwareAdapterLoadName = keyof typeof LABWARE_ADAPTER_LOADNAME_PATHS
diff --git a/components/src/hardware-sim/ProtocolDeck/index.tsx b/components/src/hardware-sim/ProtocolDeck/index.tsx
index fb1ac06349b..20c3a0c990b 100644
--- a/components/src/hardware-sim/ProtocolDeck/index.tsx
+++ b/components/src/hardware-sim/ProtocolDeck/index.tsx
@@ -4,7 +4,7 @@ import {
FLEX_ROBOT_TYPE,
getLabwareDisplayName,
getSimplestDeckConfigForProtocol,
- parseInitialLoadedLabwareByAdapter,
+ getTopLabwareInfo,
} from '@opentrons/shared-data'
import { BaseDeck } from '../BaseDeck'
@@ -19,6 +19,8 @@ import type {
CompletedProtocolAnalysis,
LabwareDefinition2,
ProtocolAnalysisOutput,
+ RunTimeCommand,
+ LoadLabwareRunTimeCommand,
} from '@opentrons/shared-data'
export * from './utils/getStandardDeckViewLayerBlockList'
@@ -46,13 +48,15 @@ export function ProtocolDeck(props: ProtocolDeckProps): JSX.Element | null {
if (protocolAnalysis == null || (protocolAnalysis?.errors ?? []).length > 0)
return null
+ const commands: RunTimeCommand[] = protocolAnalysis.commands
+ const loadLabwareCommands = commands?.filter(
+ (command): command is LoadLabwareRunTimeCommand =>
+ command.commandType === 'loadLabware'
+ )
const robotType = protocolAnalysis.robotType ?? FLEX_ROBOT_TYPE
const deckConfig = getSimplestDeckConfigForProtocol(protocolAnalysis)
const labwareByLiquidId = getLabwareInfoByLiquidId(protocolAnalysis.commands)
- const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter(
- protocolAnalysis.commands
- )
const modulesInSlots = getModulesInSlots(protocolAnalysis)
const modulesOnDeck = modulesInSlots.map(
@@ -63,16 +67,10 @@ export function ProtocolDeck(props: ProtocolDeckProps): JSX.Element | null {
nestedLabwareDef,
nestedLabwareNickName,
}) => {
- const labwareInAdapterInMod =
- nestedLabwareId != null
- ? initialLoadedLabwareByAdapter[nestedLabwareId]
- : null
- // only rendering the labware on top most layer so
- // either the adapter or the labware are rendered but not both
- const topLabwareDefinition =
- labwareInAdapterInMod?.result?.definition ?? nestedLabwareDef
- const topLabwareId =
- labwareInAdapterInMod?.result?.labwareId ?? nestedLabwareId
+ const { topLabwareId, topLabwareDefinition } = getTopLabwareInfo(
+ nestedLabwareId ?? '',
+ loadLabwareCommands
+ )
return {
moduleModel,
@@ -112,15 +110,16 @@ export function ProtocolDeck(props: ProtocolDeckProps): JSX.Element | null {
}
)
+ // this function gets the top labware assuming a stack of max 2 labware
const topMostLabwareInSlots = getTopMostLabwareInSlots(protocolAnalysis)
const labwareOnDeck = topMostLabwareInSlots.map(
({ labwareId, labwareDef, labwareNickName, location }) => {
- const labwareInAdapter = initialLoadedLabwareByAdapter[labwareId]
- // only rendering the labware on top most layer so
- // either the adapter or the labware are rendered but not both
- const topLabwareDefinition =
- labwareInAdapter?.result?.definition ?? labwareDef
- const topLabwareId = labwareInAdapter?.result?.labwareId ?? labwareId
+ // this gets the very top of the stack in case there is a stack
+ // of many like items, such as TC lids
+ const { topLabwareId, topLabwareDefinition } = getTopLabwareInfo(
+ labwareId,
+ loadLabwareCommands
+ )
const isLabwareInStack = protocolAnalysis?.commands.some(
command =>
command.commandType === 'loadLabware' &&
@@ -146,7 +145,7 @@ export function ProtocolDeck(props: ProtocolDeckProps): JSX.Element | null {
highlight: handleLabwareClick != null,
highlightShadow: handleLabwareClick != null && isLabwareInStack,
onLabwareClick:
- handleLabwareClick != null
+ handleLabwareClick != null && topLabwareDefinition != null
? () => {
handleLabwareClick(topLabwareDefinition, topLabwareId)
}
diff --git a/shared-data/js/helpers/parseProtocolCommands.ts b/shared-data/js/helpers/parseProtocolCommands.ts
index a5a05a65636..4f111e4d3e5 100644
--- a/shared-data/js/helpers/parseProtocolCommands.ts
+++ b/shared-data/js/helpers/parseProtocolCommands.ts
@@ -10,6 +10,7 @@ import type {
LoadModuleRunTimeCommand,
LoadPipetteRunTimeCommand,
RunTimeCommand,
+ LabwareLocation,
} from '../../command/types'
import type { PipetteName } from '../pipettes'
import type {
@@ -18,6 +19,7 @@ import type {
LoadedModule,
LoadedPipette,
ModuleModel,
+ LabwareDefinition2,
} from '../types'
interface PipetteNamesByMount {
@@ -135,6 +137,111 @@ export function parseInitialLoadedLabwareBySlot(
)
}
+// given a labware id and load labware commands, this function
+// finds the top most labware in the stack and returns relevant
+// information
+export function getTopLabwareInfo(
+ labwareId: string,
+ loadLabwareCommands: LoadLabwareRunTimeCommand[],
+ currentStackHeight: number = 0
+): {
+ topLabwareId: string
+ topLabwareDefinition?: LabwareDefinition2
+ topLabwareDisplayName?: string
+} {
+ const nestedCommand = loadLabwareCommands.find(
+ command =>
+ command.commandType === 'loadLabware' &&
+ command.params.location !== 'offDeck' &&
+ 'labwareId' in command.params.location &&
+ command.params.location.labwareId === labwareId
+ )
+ // prevent recurssion errors (like labware stacked on itself)
+ // by enforcing a max stack height
+ if (nestedCommand == null || currentStackHeight > 5) {
+ const loadCommand = loadLabwareCommands.find(
+ command =>
+ command.commandType === 'loadLabware' &&
+ command.result?.labwareId === labwareId
+ )
+ if (loadCommand == null) {
+ console.warn(
+ `could not find the load labware command assosciated with thie labwareId: ${labwareId}`
+ )
+ }
+ return {
+ topLabwareId: labwareId,
+ topLabwareDefinition: loadCommand?.result?.definition,
+ topLabwareDisplayName: loadCommand?.params.displayName,
+ }
+ } else {
+ return getTopLabwareInfo(
+ nestedCommand?.result?.labwareId as string,
+ loadLabwareCommands,
+ currentStackHeight + 1
+ )
+ }
+}
+
+// this recursive function will parse through load labware commands
+// and give the quantity of LIKE labware stacked below the given labware
+// id and the labware location of the bottom-most stacked item
+export function getLabwareStackCountAndLocation(
+ topLabwareId: string,
+ commands: RunTimeCommand[],
+ initialQuantity: number = 1
+): { labwareQuantity: number; labwareLocation: LabwareLocation } {
+ const loadLabwareCommands = commands?.filter(
+ (command): command is LoadLabwareRunTimeCommand =>
+ command.commandType === 'loadLabware'
+ )
+ const loadLabwareCommand = loadLabwareCommands?.find(
+ command => command.result?.labwareId === topLabwareId
+ )
+
+ if (loadLabwareCommands == null || loadLabwareCommand == null) {
+ console.warn(
+ `could not find the load labware command assosciated with thie labwareId: ${topLabwareId}`
+ )
+ return { labwareLocation: 'offDeck', labwareQuantity: 0 }
+ }
+
+ const labwareLocation = loadLabwareCommand.params.location
+
+ if (labwareLocation !== 'offDeck' && 'labwareId' in labwareLocation) {
+ const lowerLabwareCommand = loadLabwareCommands?.find(command =>
+ command.result != null
+ ? command.result?.labwareId === labwareLocation.labwareId
+ : ''
+ )
+ if (lowerLabwareCommand?.result?.labwareId == null) {
+ console.warn(
+ `could not find the load labware command assosciated with thie labwareId: ${labwareLocation.labwareId}`
+ )
+ return { labwareLocation: 'offDeck', labwareQuantity: 0 }
+ }
+
+ const isSameLabware =
+ loadLabwareCommand.params.loadName ===
+ lowerLabwareCommand?.params.loadName
+
+ // add protection for recursion errors by having a max stack of 5 which is current
+ // allowed max stack of TC lids
+ if (isSameLabware && initialQuantity < 5) {
+ const newQuantity = initialQuantity + 1
+ return getLabwareStackCountAndLocation(
+ lowerLabwareCommand.result.labwareId,
+ commands,
+ newQuantity
+ )
+ }
+ }
+ return {
+ labwareQuantity: initialQuantity,
+ labwareLocation,
+ }
+}
+
export interface LoadedLabwareByAdapter {
[labwareId: string]: LoadLabwareRunTimeCommand
}