Skip to content

Commit abc2673

Browse files
authored
feat(protocol-designer): the labware belongs on the hopper, so I put it there (#20316)
closes EXEC-1441 EXEC-1443
1 parent 78ef965 commit abc2673

File tree

20 files changed

+351
-95
lines changed

20 files changed

+351
-95
lines changed

protocol-designer/src/assets/localization/en/shared.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
"slot_detail": "Slot Detail",
123123
"software_manual": "Software manual",
124124
"source_well": "Source well",
125+
"stacker": "Stacker {{slot}}",
125126
"stagingArea": "Staging area",
126127
"start_point": "Edit {{prefix}} start point",
127128
"step_count": "Step {{current}}",

protocol-designer/src/components/organisms/SlotDetailsContainer/index.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { useTranslation } from 'react-i18next'
22
import { useSelector } from 'react-redux'
33

44
import { getModuleDisplayName } from '@opentrons/shared-data'
5-
import { getTopLocationInStack } from '@opentrons/step-generation'
5+
import {
6+
FAKE_HOPPER_LOCATION_MAP,
7+
getIsSlotAHopper,
8+
getTopLocationInStack,
9+
} from '@opentrons/step-generation'
610

711
import { getLiquidEntities } from '/protocol-designer/step-forms/selectors'
812
import { getDeckSetupForActiveItem } from '/protocol-designer/top-selectors/labware-locations'
@@ -13,6 +17,7 @@ import { getFullStackFromLabwaresOnDeck } from '/protocol-designer/utils'
1317
import { SlotInformation } from '../SlotInformation'
1418

1519
import type { DeckSlotId, RobotType } from '@opentrons/shared-data'
20+
import type { HopperLocationMapKey } from '@opentrons/step-generation'
1621
import type { ContentsByWell } from '/protocol-designer/labware-ingred/types'
1722

1823
interface SlotDetailContainerProps {
@@ -32,10 +37,13 @@ export function SlotDetailsContainer(
3237
)
3338
const nickNames = useSelector(uiLabwareSelectors.getLabwareNicknamesById)
3439
const liquidEntities = useSelector(getLiquidEntities)
35-
3640
if (slot == null || (slot === 'offDeck' && offDeckLabwareId == null)) {
3741
return null
3842
}
43+
const isSlotAHopper = getIsSlotAHopper(slot)
44+
const adjustedSlotToFindModule = isSlotAHopper
45+
? FAKE_HOPPER_LOCATION_MAP[slot as HopperLocationMapKey]
46+
: slot
3947

4048
const {
4149
modules: deckSetupModules,
@@ -47,11 +55,12 @@ export function SlotDetailsContainer(
4755
offDeckLabwareId != null ? nickNames[offDeckLabwareId] : null
4856

4957
const moduleOnSlot = Object.values(deckSetupModules).find(
50-
module => module.slot === slot
58+
module => module.slot === adjustedSlotToFindModule
5159
)
5260
const fullStackFromLabwares = getFullStackFromLabwaresOnDeck(
5361
Object.values(deckSetupLabwares),
54-
slot
62+
slot,
63+
isSlotAHopper
5564
)
5665
const topLocationLabwareId =
5766
fullStackFromLabwares?.length > 0

protocol-designer/src/components/organisms/SlotInformation/index.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ import {
2121
THERMOCYCLER_MODULE_V1,
2222
THERMOCYCLER_MODULE_V2,
2323
} from '@opentrons/shared-data'
24+
import {
25+
FAKE_HOPPER_LOCATION_MAP,
26+
getIsSlotAHopper,
27+
} from '@opentrons/step-generation'
2428

2529
import { LINE_CLAMP_TEXT_STYLE } from '/protocol-designer/components/atoms'
2630
import { useDeckSetupWindowBreakPoint } from '/protocol-designer/pages/Designer/DeckSetup/utils'
2731

2832
import type { FC } from 'react'
2933
import type { RobotType } from '@opentrons/shared-data'
34+
import type { HopperLocationMapKey } from '@opentrons/step-generation'
3035

3136
interface SlotInformationProps {
3237
location: string
@@ -51,11 +56,18 @@ export const SlotInformation: FC<SlotInformationProps> = ({
5156
robotType === FLEX_ROBOT_TYPE
5257
? TC_MODULE_LOCATION_OT3
5358
: TC_MODULE_LOCATION_OT2
54-
const modifiedLocation =
59+
60+
let modifiedLocation = location
61+
if (
5562
modules.includes(getModuleDisplayName(THERMOCYCLER_MODULE_V2)) ||
5663
modules.includes(getModuleDisplayName(THERMOCYCLER_MODULE_V1))
57-
? tcDisplayLocation
58-
: location
64+
) {
65+
modifiedLocation = tcDisplayLocation
66+
} else if (getIsSlotAHopper(location)) {
67+
modifiedLocation = t('stacker', {
68+
slot: FAKE_HOPPER_LOCATION_MAP[location as HopperLocationMapKey],
69+
})
70+
}
5971

6072
return (
6173
<Flex

protocol-designer/src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,6 @@ export const CHANNELS_MAPPED_TO_MAX_SPEED: Record<
163163
export const MINIMUM_LIQUID_CLASS_VOLUME = 1
164164

165165
export const ACCEPTED_PROTOCOL_FILE_TYPES = '.json,.py'
166+
167+
export const HOPPER_LABWARE_X_OFFSET = 178
168+
export const HOPPER_ZOOM_OFFSET_POSTITION = 230

protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import {
3030
WASTE_CHUTE_CUTOUT,
3131
} from '@opentrons/shared-data'
3232

33-
import { DECK_SETUP_TOOLS_WIDTH_REM } from '../../../constants'
33+
import {
34+
DECK_SETUP_TOOLS_WIDTH_REM,
35+
HOPPER_ZOOM_OFFSET_POSTITION,
36+
} from '../../../constants'
3437
import { getDisableModuleRestrictions } from '../../../feature-flags/selectors'
3538
import {
3639
editSlotInfo,
@@ -142,24 +145,30 @@ export function DeckSetupContainer(
142145
return i < viewBoxNumerical.length - 1 ? acc + `${num} ` : acc + `${num}`
143146
}, '')
144147

145-
const addEquipment = (slotId: string): void => {
148+
const addEquipment = (location: string): void => {
149+
const isOnHopper = location.includes('hopper')
150+
const slot = isOnHopper ? location.split('hopper')[1] : location
146151
const { createdModuleForSlot, preSelectedFixture } = getSlotInformation({
147152
deckSetup: activeDeckSetup,
148-
slot: slotId,
153+
slot: location,
149154
deckDef,
150155
})
151156

152157
const cutoutId =
153158
getCutoutIdForAddressableArea(
154-
slotId as AddressableAreaName,
159+
slot as AddressableAreaName,
155160
deckDef.cutoutFixtures
156161
) ?? null
157162
if (cutoutId == null) {
158163
console.error('expected to find a cutoutId but could not')
159164
}
160-
dispatch(selectZoomedIntoSlot({ slot: slotId, cutout: cutoutId }))
165+
dispatch(selectZoomedIntoSlot({ slot: location, cutout: cutoutId }))
161166

162-
const zoomInSlotPosition = getPositionFromSlotId(slotId ?? '', deckDef)
167+
const zoomInSlotPosition = getPositionFromSlotId(
168+
slot ?? '',
169+
deckDef,
170+
...(isOnHopper ? [HOPPER_ZOOM_OFFSET_POSTITION] : [])
171+
)
163172
if (zoomInSlotPosition != null) {
164173
const zoomedInViewBox = zoomInOnCoordinate({
165174
x: zoomInSlotPosition[0],
@@ -210,7 +219,6 @@ export function DeckSetupContainer(
210219
)
211220

212221
const svgContainerWidth = getSVGContainerWidth(robotType, isZoomed)
213-
214222
return (
215223
<>
216224
<Flex

protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ import {
1313
isAddressableAreaStandardSlot,
1414
THERMOCYCLER_MODULE_TYPE,
1515
} from '@opentrons/shared-data'
16-
import { getSlotInLocationStack } from '@opentrons/step-generation'
16+
import {
17+
FAKE_HOPPER_LOCATION_MAP,
18+
getIsSlotAHopper,
19+
getSlotInLocationStack,
20+
} from '@opentrons/step-generation'
21+
22+
import { HOPPER_LABWARE_X_OFFSET } from '/protocol-designer/constants'
1723

1824
import { LabwareOnDeck } from '../../../components/organisms'
1925
import { getSlotsWithCollisions } from '../../../components/organisms/utils'
@@ -53,6 +59,7 @@ import type {
5359
DeckSlotId,
5460
} from '@opentrons/shared-data'
5561
import type {
62+
HopperLocationMapKey,
5663
ModuleTemporalProperties,
5764
ThermocyclerModuleState,
5865
} from '@opentrons/step-generation'
@@ -86,6 +93,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
8693
showGen1MultichannelCollisionWarnings,
8794
stagingAreaCutoutIds,
8895
} = props
96+
const { labware: activeLabware } = activeDeckSetup
8997
const robotType = useSelector(getRobotType)
9098
const slotIdsBlockedBySpanning = getSlotIdsBlockedBySpanningForThermocycler(
9199
activeDeckSetup,
@@ -118,7 +126,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
118126
draggedLabware,
119127
})
120128
const swapBlockedAdapter = getSwapBlockedAdapter({
121-
labwareById: activeDeckSetup.labware,
129+
labwareById: activeLabware,
122130
hoveredLabware,
123131
draggedLabware,
124132
})
@@ -134,6 +142,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
134142
createdModuleForSlot,
135143
preSelectedFixture,
136144
slotPosition,
145+
isSlotAHopper,
137146
} = useMemo(() => {
138147
return getSlotInformation({
139148
deckSetup: activeDeckSetup,
@@ -142,8 +151,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
142151
})
143152
}, [activeDeckSetup, selectedZoomInSlot])
144153

145-
const createdTopLabwareForSlot =
146-
activeDeckSetup.labware[createdStackForSlot[0]]
154+
const createdTopLabwareForSlot = activeLabware[createdStackForSlot[0]]
147155
const amount = createdStackForSlot?.length ?? 1
148156
// initiate the slot's info
149157
useEffect(() => {
@@ -171,11 +179,22 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
171179
selectedZoomInSlot,
172180
])
173181

174-
const allLabware = Object.values(activeDeckSetup.labware)
182+
const allLabware = Object.values(activeLabware)
175183

176184
const allModules: ModuleOnDeck[] = values(activeDeckSetup.modules)
177-
const menuListSlotPosition = getPositionFromSlotId(menuListId ?? '', deckDef)
178-
185+
const isMenuListIdForHopper =
186+
menuListId != null && getIsSlotAHopper(menuListId)
187+
const adjustedMenuListId = isMenuListIdForHopper
188+
? FAKE_HOPPER_LOCATION_MAP[menuListId as HopperLocationMapKey]
189+
: menuListId
190+
const menuListSlotPosition =
191+
adjustedMenuListId != null
192+
? getPositionFromSlotId(
193+
adjustedMenuListId as string,
194+
deckDef,
195+
...(isMenuListIdForHopper ? [HOPPER_LABWARE_X_OFFSET] : [])
196+
)
197+
: null
179198
const multichannelWarningSlotIds: AddressableAreaName[] =
180199
showGen1MultichannelCollisionWarnings
181200
? getSlotsWithCollisions(deckDef, allModules)
@@ -186,7 +205,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
186205
? getAdjacentLabware(
187206
preSelectedFixture,
188207
selectedSlot.cutout,
189-
activeDeckSetup.labware
208+
activeLabware
190209
)
191210
: null
192211

@@ -245,10 +264,8 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
245264
}
246265
}
247266

248-
const { topMostId, rightBelowTopId } = getLabwaresOnModuleFromStack(
249-
moduleOnDeck.id,
250-
allLabware
251-
)
267+
const { topMostId, rightBelowTopId, hopperTopMostId } =
268+
getLabwaresOnModuleFromStack(moduleOnDeck.id, allLabware)
252269
const labwareInterfaceBoundingBox = {
253270
xDimension: moduleDef.dimensions.labwareInterfaceXDimension ?? 0,
254271
yDimension: moduleDef.dimensions.labwareInterfaceYDimension ?? 0,
@@ -273,11 +290,9 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
273290
}
274291
: tempInnerProps
275292
const labwareOnModule =
276-
topMostId != null ? activeDeckSetup.labware[topMostId] : null
293+
topMostId != null ? activeLabware[topMostId] : null
277294
const labwareRightBelowTopMostLabware =
278-
rightBelowTopId != null
279-
? activeDeckSetup.labware[rightBelowTopId]
280-
: null
295+
rightBelowTopId != null ? activeLabware[rightBelowTopId] : null
281296
const isAdapter = labwareOnModule?.def.allowedRoles?.includes('adapter')
282297

283298
return moduleOnDeck.slot !== selectedSlot.slot ? (
@@ -299,6 +314,34 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
299314
: 'offsetToSlot'
300315
}
301316
>
317+
{hopperTopMostId != null ? (
318+
<>
319+
<LabwareOnDeck
320+
x={HOPPER_LABWARE_X_OFFSET}
321+
y={0}
322+
labwareOnDeck={activeLabware[hopperTopMostId]}
323+
/>
324+
<HighlightLabware
325+
labwareOnDeck={activeLabware[hopperTopMostId]}
326+
position={[HOPPER_LABWARE_X_OFFSET, 0, 0]}
327+
isZoomed={selectedZoomInSlot != null}
328+
/>
329+
<LabwareControls
330+
terminalItemId={terminalItemId}
331+
itemId={`hopper${slotId}`}
332+
setHover={setHover}
333+
setShowMenuListForId={setShowMenuListForId}
334+
hover={hover}
335+
slotPosition={[HOPPER_LABWARE_X_OFFSET, 0, 0]} // Module Component already handles nested positioning
336+
setHoveredLabware={setHoveredLabware}
337+
setDraggedLabware={setDraggedLabware}
338+
// TODO: disallow the ability to drag/drop labware from the hopper and shuttle
339+
swapBlocked={false}
340+
labwareOnDeck={activeLabware[hopperTopMostId]}
341+
isSelected={selectedZoomInSlot != null}
342+
/>
343+
</>
344+
) : null}
302345
{labwareOnModule != null &&
303346
!isLabwareOccludedByThermocyclerLid ? (
304347
<>
@@ -381,6 +424,26 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
381424
addEquipment={addEquipment}
382425
/>
383426
) : null}
427+
{hopperTopMostId == null &&
428+
moduleOnDeck.type === FLEX_STACKER_MODULE_TYPE ? (
429+
<SlotControls
430+
terminalItemId={terminalItemId}
431+
itemId={`hopper${slotId}`}
432+
key={`${moduleOnDeck.slot}_flexHopper`}
433+
slotPosition={[HOPPER_LABWARE_X_OFFSET, 0, 0]}
434+
slotBoundingBox={labwareInterfaceBoundingBox}
435+
moduleType={moduleOnDeck.type}
436+
handleDragHover={handleHoverEmptySlot}
437+
slotId={moduleOnDeck.id}
438+
hover={hover}
439+
setHover={setHover}
440+
setShowMenuListForId={setShowMenuListForId}
441+
isSelected={selectedZoomInSlot != null}
442+
deckDef={deckDef}
443+
stagingAreaAddressableAreas={[]}
444+
addEquipment={addEquipment}
445+
/>
446+
) : null}
384447
</Module>
385448
</Fragment>
386449
) : null
@@ -431,6 +494,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
431494
const moduleOnSlot = Object.values(activeDeckSetup.modules).find(
432495
module => module.slot === addressableArea.id
433496
)
497+
434498
return (
435499
<SlotControls
436500
terminalItemId={terminalItemId}
@@ -465,7 +529,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
465529
}
466530
const slot = getSlotInLocationStack(labware.stack)
467531
const labwareAmount = labware.stack.reduce(
468-
(amount, item) => amount + (activeDeckSetup.labware[item] ? 1 : 0),
532+
(amount, item) => amount + (activeLabware[item] ? 1 : 0),
469533
0
470534
)
471535
const isTopLabware = labware.stack[0] === labware.id
@@ -649,6 +713,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
649713
deckDef={deckDef}
650714
robotType={robotType}
651715
slotPosition={slotPosition}
716+
isSlotAHopper={isSlotAHopper}
652717
/>
653718

654719
{/* slot overflow menu */}

0 commit comments

Comments
 (0)