Skip to content

Commit 9676298

Browse files
smb2268koji
authored andcommitted
feat(protocol-designer): Make adjustments to display flex stacker in deck maps (#19959)
This PR enables you to add a flex stacker to a PD protocol, properly visualize it on all deck maps, and add compatible labware to the shuttle position. Adjust deck thumbnail, zoomed deck map, and deck edit screens to adapt to the stacker's extra footprint (all through manual visual checking unfortunately) Create list of Stacker recommended and compatible labware for addition in the
1 parent f1f8247 commit 9676298

File tree

10 files changed

+126
-18
lines changed

10 files changed

+126
-18
lines changed

components/src/hardware-sim/RobotCoordinateSpace/RobotCoordinateSpaceWithRef.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,30 @@ interface RobotCoordinateSpaceWithRefProps extends ComponentProps<typeof Svg> {
1313
viewBox?: string | null
1414
deckDef?: DeckDefinition
1515
zoomed?: boolean
16+
adjustViewboxForStacker?: boolean
1617
children?: (props: RobotCoordinateSpaceWithRefRenderProps) => ReactNode
1718
}
1819

20+
// manual visual adjustments for flex stacker deck view to fit properly and
21+
// in the center of the frame
22+
const STACKER_VIEWBOX_ADJUSTMENTS = {
23+
viewBoxOriginX: 260,
24+
viewBoxOriginY: -25,
25+
deckXDimension: -255,
26+
deckYDimension: 105,
27+
}
28+
1929
export function RobotCoordinateSpaceWithRef(
2030
props: RobotCoordinateSpaceWithRefProps
2131
): JSX.Element | null {
22-
const { children, deckDef, viewBox, zoomed = false, ...restProps } = props
32+
const {
33+
children,
34+
deckDef,
35+
viewBox,
36+
adjustViewBoxForStacker = false,
37+
zoomed = false,
38+
...restProps
39+
} = props
2340
const wrapperRef = useRef<SVGSVGElement>(null)
2441

2542
if (deckDef == null && viewBox == null) return null
@@ -34,7 +51,21 @@ export function RobotCoordinateSpaceWithRef(
3451
(acc, deckSlot) => ({ ...acc, [deckSlot.id]: deckSlot }),
3552
{}
3653
)
37-
wholeDeckViewBox = `${viewBoxOriginX} ${viewBoxOriginY} ${deckXDimension} ${deckYDimension}`
54+
const base = [
55+
viewBoxOriginX,
56+
viewBoxOriginY,
57+
deckXDimension,
58+
deckYDimension,
59+
] as const
60+
const adj = [
61+
base[0] + STACKER_VIEWBOX_ADJUSTMENTS.viewBoxOriginX,
62+
base[1] + STACKER_VIEWBOX_ADJUSTMENTS.viewBoxOriginY,
63+
base[2] + STACKER_VIEWBOX_ADJUSTMENTS.deckXDimension,
64+
base[3] + STACKER_VIEWBOX_ADJUSTMENTS.deckYDimension,
65+
] as const
66+
67+
const adjustedViewBox = adjustViewBoxForStacker ? adj : base
68+
wholeDeckViewBox = `${adjustedViewBox[0]} ${adjustedViewBox[1]} ${adjustedViewBox[2]} ${adjustedViewBox[3]}`
3869
}
3970

4071
return (

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
WasteChuteStagingAreaFixture,
2222
} from '@opentrons/components'
2323
import {
24+
FLEX_STACKER_MODULE_TYPE,
2425
getPositionFromSlotId,
2526
isAddressableAreaStandardSlot,
2627
OT2_ROBOT_TYPE,
@@ -126,7 +127,9 @@ export function DeckSetupContainer(
126127
(windowInnerWidthRem - DECK_SETUP_TOOLS_WIDTH_REM) / windowInnerWidthRem,
127128
2
128129
)
129-
130+
const hasFlexStacker = Object.values(activeDeckSetup.modules).some(
131+
module => module.type === FLEX_STACKER_MODULE_TYPE
132+
)
130133
const isZoomed = Object.values(zoomIn).some(val => val != null)
131134
const viewBoxNumerical = viewBox?.split(' ').map(val => Number(val)) ?? []
132135
const viewBoxAdjustedNumerical = [
@@ -248,6 +251,7 @@ export function DeckSetupContainer(
248251
}
249252
outline="auto"
250253
zoomed={zoomIn.slot != null}
254+
adjustViewBoxForStacker={hasFlexStacker}
251255
borderRadius={BORDERS.borderRadius12}
252256
>
253257
{() => (

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import values from 'lodash/values'
44

55
import { DeckLabelSet, Module } from '@opentrons/components'
66
import {
7+
FLEX_STACKER_MODULE_TYPE,
78
getAddressableAreaFromSlotId,
89
getModuleDef,
910
getPositionFromSlotId,
@@ -292,7 +293,11 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
292293
innerProps={innerProps}
293294
targetSlotId={slotId}
294295
targetDeckId={deckDef.otId}
295-
childrenPositioningMode="offsetToSlot"
296+
childrenPositioningMode={
297+
moduleOnDeck.type === FLEX_STACKER_MODULE_TYPE
298+
? 'passThrough'
299+
: 'offsetToSlot'
300+
}
296301
>
297302
{labwareOnModule != null &&
298303
!isLabwareOccludedByThermocyclerLid ? (

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { useSelector } from 'react-redux'
22

33
import { Module } from '@opentrons/components'
44
import {
5+
FLEX_STACKER_MODULE_TYPE,
56
getAllDefinitions,
67
getModuleDef,
8+
getModuleType,
79
inferModuleOrientationFromXCoordinate,
810
} from '@opentrons/shared-data'
911
import { getSlotInLocationStack } from '@opentrons/step-generation'
@@ -133,7 +135,11 @@ export const SelectedItems = (props: SelectedItemsProps): JSX.Element => {
133135
orientation={orientation}
134136
targetDeckId={null}
135137
targetSlotId={null}
136-
childrenPositioningMode="offsetToSlot"
138+
childrenPositioningMode={
139+
getModuleType(selectedModuleModel) === FLEX_STACKER_MODULE_TYPE
140+
? 'passThrough'
141+
: 'offsetToSlot'
142+
}
137143
>
138144
<SelectedModuleLabwareRender
139145
topLabwareOnDeck={matchingSelectedTopLabwareOnDeck}

protocol-designer/src/pages/Designer/DeckSetup/constants.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,23 @@ export const RECOMMENDED_LABWARE_BY_MODULE: { [K in ModuleType]: string[] } = {
117117
'opentrons_96_wellplate_200ul_pcr_full_skirt',
118118
],
119119
[ABSORBANCE_READER_TYPE]: ['nest_96_wellplate_200ul_flat'],
120-
[FLEX_STACKER_MODULE_TYPE]: [],
120+
[FLEX_STACKER_MODULE_TYPE]: [
121+
// all flex tipracks
122+
'opentrons_flex_96_filtertiprack_1000ul',
123+
'opentrons_flex_96_filtertiprack_200ul',
124+
'opentrons_flex_96_filtertiprack_50ul',
125+
'opentrons_flex_96_tiprack_1000ul',
126+
'opentrons_flex_96_tiprack_200ul',
127+
'opentrons_flex_96_tiprack_50ul',
128+
// tested and verified well plates
129+
'opentrons_96_wellplate_200ul_pcr_full_skirt',
130+
'biorad_96_wellplate_200ul_pcr',
131+
'biorad_384_wellplate_50ul',
132+
'corning_24_wellplate_3.4ml_flat',
133+
'nest_96_wellplate_2ml_deep',
134+
'nest_96_wellplate_100ul_pcr_full_skirt',
135+
'nest_96_wellplate_200ul_flat',
136+
],
121137
}
122138

123139
export const MOAM_MODELS_WITH_FF: ModuleModel[] = [TEMPERATURE_MODULE_V2]

protocol-designer/src/pages/Designer/DeckSetup/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -502,18 +502,18 @@ interface HoverDimensions {
502502
const FOURTH_COLUMN_SLOTS = ['A4', 'B4', 'C4', 'D4']
503503

504504
export const getFlexHoverDimensions = (
505-
stagingAreaLocations: string[],
505+
columnFourLocations: string[],
506506
cutoutId: CutoutId,
507507
slotId: string,
508508
hasTCOnSlot: boolean,
509509
slotPosition: CoordinateTuple
510510
): HoverDimensions => {
511-
const hasStagingArea = stagingAreaLocations.includes(cutoutId)
511+
const columnFourIsOccupied = columnFourLocations.includes(cutoutId)
512512

513513
const X_ADJUSTMENT_LEFT_SIDE = -101.5
514514
const X_ADJUSTMENT = -17
515515
const X_DIMENSION_MIDDLE_SLOTS = 160.3
516-
const X_DIMENSION_OUTER_SLOTS = hasStagingArea ? 160.0 : 246.5
516+
const X_DIMENSION_OUTER_SLOTS = columnFourIsOccupied ? 160.0 : 246.5
517517
const X_DIMENSION_4TH_COLUMN_SLOTS = 175.0
518518
const Y_DIMENSION = hasTCOnSlot ? 294.0 : 106.0
519519

protocol-designer/src/pages/ProtocolOverview/DeckThumbnail.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import {
2020
} from '@opentrons/components'
2121
import {
2222
FLEX_ROBOT_TYPE,
23+
FLEX_STACKER_MODULE_TYPE,
2324
getCutoutIdForAddressableArea,
2425
getDeckDefFromRobotType,
26+
getModuleType,
2527
isAddressableAreaStandardSlot,
2628
OT2_ROBOT_TYPE,
2729
STAGING_AREA_CUTOUTS,
@@ -38,6 +40,7 @@ import type { CutoutId, DeckSlotId, RobotType } from '@opentrons/shared-data'
3840
import type { AdditionalEquipmentEntity } from '@opentrons/step-generation'
3941

4042
const RIGHT_COLUMN_FIXTURE_PADDING = 50 // mm
43+
const FLEX_STACKER_FIXTURE_PADDING = 220 // mm
4144
const WASTE_CHUTE_SPACE = 30
4245
const OT2_STANDARD_DECK_VIEW_LAYER_BLOCK_LIST: string[] = [
4346
'calibrationMarkings',
@@ -99,11 +102,18 @@ export function DeckThumbnail(props: DeckThumbnailProps): JSX.Element {
99102
const hasWasteChute =
100103
wasteChuteFixtures.length > 0 || wasteChuteStagingAreaFixtures.length > 0
101104

105+
const hasFlexStacker = Object.values(initialDeckSetup.modules).some(
106+
module => getModuleType(module.model) === FLEX_STACKER_MODULE_TYPE
107+
)
102108
const filteredAddressableAreas = deckDef.locations.addressableAreas.filter(
103109
aa => isAddressableAreaStandardSlot(aa.id, deckDef)
104110
)
105111
const hasRightColumnFixtures =
106-
stagingAreaFixtures.length + wasteChuteFixtures.length > 0
112+
stagingAreaFixtures.length + wasteChuteFixtures.length > 0 || hasFlexStacker
113+
const rightColumnAdjustment = hasFlexStacker
114+
? FLEX_STACKER_FIXTURE_PADDING
115+
: RIGHT_COLUMN_FIXTURE_PADDING
116+
107117
return (
108118
<Flex
109119
width="100%"
@@ -125,7 +135,7 @@ export function DeckThumbnail(props: DeckThumbnailProps): JSX.Element {
125135
: deckDef.cornerOffsetFromOrigin[1]
126136
} ${
127137
hasRightColumnFixtures
128-
? deckDef.dimensions[0] + RIGHT_COLUMN_FIXTURE_PADDING
138+
? deckDef.dimensions[0] + rightColumnAdjustment
129139
: deckDef.dimensions[0]
130140
} ${deckDef.dimensions[1]}`}
131141
zoomed

protocol-designer/src/pages/ProtocolOverview/DeckThumbnailDetails.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import values from 'lodash/values'
33

44
import { Module } from '@opentrons/components'
55
import {
6+
FLEX_STACKER_MODULE_TYPE,
67
getAddressableAreaFromSlotId,
78
getModuleDef,
89
getPositionFromSlotId,
@@ -91,7 +92,11 @@ export const DeckThumbnailDetails = (
9192
}
9293
targetSlotId={slotId}
9394
targetDeckId={deckDef.otId}
94-
childrenPositioningMode="offsetToSlot"
95+
childrenPositioningMode={
96+
moduleState.type === FLEX_STACKER_MODULE_TYPE
97+
? 'passThrough'
98+
: 'offsetToSlot'
99+
}
95100
>
96101
<>
97102
{rightBelowTopId != null ? (

protocol-designer/src/pages/ProtocolOverview/SlotHover.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import {
1212
} from '@opentrons/components'
1313
import {
1414
FLEX_ROBOT_TYPE,
15+
FLEX_STACKER_MODULE_TYPE,
1516
getCutoutIdForAddressableArea,
17+
getCutoutIdForSlotName,
1618
getDeckDefFromRobotType,
19+
getModuleType,
1720
THERMOCYCLER_MODULE_TYPE,
1821
} from '@opentrons/shared-data'
1922

@@ -48,10 +51,20 @@ export function SlotHover(props: SlotHoverProps): JSX.Element | null {
4851
module => module.slot === slotId && module.type === THERMOCYCLER_MODULE_TYPE
4952
)
5053
const tcSlots = robotType === FLEX_ROBOT_TYPE ? ['A1'] : ['8', '10', '11']
51-
const stagingAreaLocations = Object.values(additionalEquipmentOnDeck)
54+
const columnFourLocations = Object.values(additionalEquipmentOnDeck)
5255
.filter(ae => ae.name === 'stagingArea')
5356
?.map(ae => ae.location as string)
5457

58+
Object.values(modules).reduce<string[]>((acc, module) => {
59+
if (getModuleType(module.model) === FLEX_STACKER_MODULE_TYPE) {
60+
const cutoutForStacker = getCutoutIdForSlotName(module.slot, deckDef)
61+
if (cutoutForStacker != null) {
62+
acc.push(cutoutForStacker)
63+
}
64+
}
65+
return acc
66+
}, columnFourLocations)
67+
5568
const cutoutId =
5669
getCutoutIdForAddressableArea(
5770
slotId as AddressableAreaName,
@@ -78,7 +91,7 @@ export function SlotHover(props: SlotHoverProps): JSX.Element | null {
7891

7992
if (robotType === FLEX_ROBOT_TYPE) {
8093
const { width, height, x, y } = getFlexHoverDimensions(
81-
stagingAreaLocations,
94+
columnFourLocations,
8295
cutoutId,
8396
slotId,
8497
hasTCOnSlot != null,

protocol-designer/src/utils/labwareModuleCompatibility.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
THERMOCYCLER_MODULE_TYPE,
1010
} from '@opentrons/shared-data'
1111

12+
import { RECOMMENDED_LABWARE_BY_MODULE } from '../pages/Designer/DeckSetup/constants'
13+
1214
import type { LabwareDefinition2, ModuleType } from '@opentrons/shared-data'
1315
import type { LabwareDefByDefURI } from '../labware-defs'
1416
import type { LabwareOnDeck } from '../step-forms'
@@ -77,7 +79,9 @@ export const COMPATIBLE_LABWARE_ALLOWLIST_BY_MODULE_TYPE: Record<
7779
[ABSORBANCE_READER_TYPE]: [
7880
'opentrons_flex_lid_absorbance_plate_reader_module',
7981
],
80-
[FLEX_STACKER_MODULE_TYPE]: [],
82+
[FLEX_STACKER_MODULE_TYPE]: [
83+
...RECOMMENDED_LABWARE_BY_MODULE[FLEX_STACKER_MODULE_TYPE],
84+
],
8185
}
8286
export const getLabwareIsCompatible = (
8387
def: LabwareDefinition2,
@@ -113,13 +117,27 @@ const _getLabwareCompatibleWithAbsorbanceReader = (
113117
)
114118
}
115119

120+
const _getLabwareCompatibleWithFlexStacker = (
121+
def: LabwareDefinition2
122+
): boolean =>
123+
RECOMMENDED_LABWARE_BY_MODULE[FLEX_STACKER_MODULE_TYPE].includes(
124+
def.parameters.loadName
125+
) ||
126+
def.metadata.displayCategory === 'wellPlate' ||
127+
def.metadata.displayCategory === 'reservoir'
128+
116129
export const getLabwareCompatibleWithModule = (
117130
def: LabwareDefinition2,
118131
moduleType: ModuleType
119132
): boolean => {
120-
return moduleType === ABSORBANCE_READER_TYPE
121-
? _getLabwareCompatibleWithAbsorbanceReader(def)
122-
: COMPATIBLE_LABWARE_ALLOWLIST_BY_MODULE_TYPE[moduleType].includes(
133+
switch (moduleType) {
134+
case FLEX_STACKER_MODULE_TYPE:
135+
return _getLabwareCompatibleWithFlexStacker(def)
136+
case ABSORBANCE_READER_TYPE:
137+
return _getLabwareCompatibleWithAbsorbanceReader(def)
138+
default:
139+
return COMPATIBLE_LABWARE_ALLOWLIST_BY_MODULE_TYPE[moduleType].includes(
123140
def.parameters.loadName
124141
)
142+
}
125143
}

0 commit comments

Comments
 (0)