Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
17636e4
WIP: Todo comments.
SyntaxColoring Dec 3, 2025
253fee8
Add todo comments for matchingLidDef displayName thing.
SyntaxColoring Dec 3, 2025
18cd852
Delete now unused getLabwareBackLeftBottomToOrigin() helper.
SyntaxColoring Dec 3, 2025
d4614d5
Rename labwareSchemaShims.test.ts -> positionMath.test.ts
SyntaxColoring Dec 3, 2025
025d609
Add center-alignment helpers, replacing the physical-alignment helpers.
SyntaxColoring Dec 3, 2025
9e2d976
Update Module for the new helpers.
SyntaxColoring Dec 3, 2025
3474b6c
Update BaseDeck to use new alignment helpers.
SyntaxColoring Dec 3, 2025
d5c5211
Update DeckMapContent to use new alignment helpers.
SyntaxColoring Dec 3, 2025
60e7030
Update LabwareMapView to use new alignment helpers.
SyntaxColoring Dec 3, 2025
8a7bc00
Update SetupLabwareMap to use new alignment helpers.
SyntaxColoring Dec 3, 2025
8c5fe46
Update ProtocolDeck to use new alignment helpers.
SyntaxColoring Dec 3, 2025
a3c8176
Un-deprecate getModuleParentOriginToChildSlotOrigin.
SyntaxColoring Dec 3, 2025
65ead1d
Update SetupLabwareMap to use new alignment helpers.
SyntaxColoring Dec 3, 2025
e6f991a
Update SetupModulesMap to use new alignment helpers.
SyntaxColoring Dec 3, 2025
b69f92d
Update ModulesAndDeckMapView to use new alignment helpers.
SyntaxColoring Dec 3, 2025
74519b0
Delete todo comments that don't need action here.
SyntaxColoring Dec 3, 2025
4144b2d
Leave a todo comment for labware-designer.
SyntaxColoring Dec 3, 2025
dcc573e
Leave a todo comment for positionMath.
SyntaxColoring Dec 3, 2025
30ec3b3
Fix incomplete sentence.
SyntaxColoring Dec 4, 2025
d3517c4
Fix incomplete doc comment.
SyntaxColoring Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 31 additions & 21 deletions app/src/molecules/InterventionModal/DeckMapContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { useEffect } from 'react'
import { css } from 'styled-components'

import {
AlignControlToModule,
BaseDeck,
Box,
CenterLabwareInModuleChildSlot,
CenterLabwareInSlot,
COLORS,
DIRECTION_COLUMN,
DISPLAY_FLEX,
Expand All @@ -15,10 +16,9 @@ import {
import {
FLEX_STACKER_MODULE_TYPE,
getDeckDefFromRobotType,
getLabwareViewBox,
getModuleDef,
getModuleType,
getSchema2CornerOffsetFromSlot,
getSchema2Dimensions,
} from '@opentrons/shared-data'

import type { ComponentProps } from 'react'
Expand Down Expand Up @@ -73,10 +73,16 @@ function InterventionStyleDeckMapContent(
? {
...labwareOnDeck,
labwareChildren: (
<LabwareHighlight
highlight={true}
definition={labwareOnDeck.definition}
/>
<CenterLabwareInSlot definition={labwareOnDeck.definition}>
{/*
LabwareHighlight is a valid "labware" in CenterLabwareInSlot because it
sizes and positions itself exactly like the underlying labware definition would.
*/}
<LabwareHighlight
highlight={true}
definition={labwareOnDeck.definition}
/>
</CenterLabwareInSlot>
),
}
: labwareOnDeck
Expand All @@ -96,16 +102,20 @@ function InterventionStyleDeckMapContent(
...module,
moduleChildren:
module?.nestedLabwareDefsBottomToTop.length > 0 ? (
<AlignControlToModule
// todo(mm, 2025-07-14): This <AlignControlToModule> ought to be a
// <AlignLabwareToModule>; right now, this will misalign the highlight
// for schema-3 labware definitions. Before we can do that,
// <LabwareHighlight> and probably <BaseDeck>'s labwareChildren prop
// will need to be modified.
<CenterLabwareInModuleChildSlot
deckId={deckDef.otId}
slotId={module.moduleLocation.slotName}
moduleDefinition={getModuleDef(module.moduleModel)}
labwareDefinition={
module.nestedLabwareDefsBottomToTop[
module.nestedLabwareDefsBottomToTop.length - 1
]
}
>
{/*
LabwareHighlight is a valid "labware" in CenterLabwareInModuleChildSlot because it
sizes and positions itself exactly like the underlying labware definition would.
*/}
<LabwareHighlight
highlight={true}
definition={
Expand All @@ -114,7 +124,7 @@ function InterventionStyleDeckMapContent(
]
}
/>
</AlignControlToModule>
</CenterLabwareInModuleChildSlot>
) : undefined,
}
: module
Expand Down Expand Up @@ -144,22 +154,22 @@ function DeckConfigStyleDeckMapContent({
return <>{DeckLocationSelect}</>
}

export function LabwareHighlight({
function LabwareHighlight({
highlight,
definition,
}: {
highlight: boolean
definition: LabwareDefinition
}): JSX.Element {
const { xDimension: width, yDimension: height } =
getSchema2Dimensions(definition)
const cornerOffsetFromSlot = getSchema2CornerOffsetFromSlot(definition)
// Size and position ourselves exactly like the underlying labware.
const { minX, minY, xDimension, yDimension } = getLabwareViewBox(definition)

return (
<RobotCoordsForeignDiv
x={cornerOffsetFromSlot.x}
y={cornerOffsetFromSlot.y}
{...{ width, height }}
x={minX}
y={minY}
width={xDimension}
height={yDimension}
innerDivProps={{
display: DISPLAY_FLEX,
flexDirection: DIRECTION_COLUMN,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useMemo, useState } from 'react'

import {
AlignControlToModule,
AlignToModuleChildSlot,
BaseDeck,
Box,
CenterLabwareInSlot,
DIRECTION_COLUMN,
Flex,
LabwareInfoOverlay,
Expand Down Expand Up @@ -89,6 +90,8 @@ export function SetupLabwareMap({
: null
// TODO: ja 8.27.25: find a better way to find the matching lid def without
// relying on the lidDisplayNames
// TODO: mm 12.3.25: deduplicate with other places where we're doing the same thing
// (grep for matchingLidDef)
const matchingLidDef = Object.values(labwareDefinitionsByURI).find(
uri => uri.metadata.displayName === topLabwareInfo?.lidDisplayName
)
Expand Down Expand Up @@ -142,10 +145,7 @@ export function SetupLabwareMap({
cursor="pointer"
>
{topLabwareDefinition != null && topLabwareInfo != null ? (
<AlignControlToModule
// todo(mm, 2025-07-14): This <AlignControlToModule> ought to be an
// <AlignLabwareToModule>. Right now, this will misalign the overlay
// for schema-3 labware definitions.
<AlignToModuleChildSlot
deckId={deckDef.otId}
slotId={slotName}
moduleDefinition={moduleDefinition}
Expand All @@ -163,7 +163,7 @@ export function SetupLabwareMap({
: 0
}
/>
</AlignControlToModule>
</AlignToModuleChildSlot>
) : null}
</g>
),
Expand All @@ -178,6 +178,10 @@ export function SetupLabwareMap({
topLabwareInfo != null
? labwareDefinitionsByURI[topLabwareInfo.definitionUri]
: null
// TODO: ja 8.27.25: find a better way to find the matching lid def without
// relying on the lidDisplayNames
// TODO: mm 12.3.25: deduplicate with other places where we're doing the same thing
// (grep for matchingLidDef)
const matchingLidDef = Object.values(labwareDefinitionsByURI).find(
uri => uri.metadata.displayName === topLabwareInfo?.lidDisplayName
)
Expand All @@ -199,10 +203,12 @@ export function SetupLabwareMap({
labwareChildren: (
<>
{matchingLidDef != null ? (
<LabwareRender
definition={matchingLidDef}
positioningMode="passThrough"
/>
<CenterLabwareInSlot definition={matchingLidDef}>
<LabwareRender
definition={matchingLidDef}
positioningMode="passThrough"
/>
</CenterLabwareInSlot>
) : null}
Comment on lines 199 to 206
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is basically the gist of this PR.

For context, BaseDeck will render labwareChildren at the origin of a deck slot. So, this code was rendering matchingLidDef at the origin of a deck slot.

Historically, for most labware, this has happened to work because it placed the bottom-left of the labware (the origin of the labware) at the bottom-left of the slot (the origin of the slot). However, it does not work for:

  • Non-standard-sized labware like lids, because then the labware is visually off-center. Sometimes cornerOffsetFromSlot compensated for this, but it's dubious whether that should be specified for things like lids, and as a matter of practicality, it so far hasn't been specified for lids.
  • Labware schema 3, since the origin of the labware is not its bottom-left corner.

So the solution is to always insert this intermediate CenterLabwareInSlot transformation, which understands what's being placed into what, and explicitly takes charge of aligning the child to the middle of the parent. There are different helpers for different circumstances.

BaseDeck's labwareChildren prop is only one pattern that needed to be fixed here. Codebase-wide, we need to stay vigilant against other patterns that place labware "at the slot origin." This is almost always subtle and implicit, unfortunately, which is one of the reasons I've been so gung-ho about documentation (like how labwareChildren documents the SVG origin) and deduplication (like how BaseDeck also has a nestedLabwareDefsBottomToTop prop that takes the responsibility of doing this kind of thing away from the caller).

<g
cursor="pointer"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
AlignControlToModule,
AlignToModuleChildSlot,
BaseDeck,
Box,
DIRECTION_COLUMN,
Expand Down Expand Up @@ -70,7 +70,7 @@ export const SetupModulesMap = ({
moduleModel: module.moduleDef.model,
moduleLocation: { slotName: module.slotName },
moduleChildren: (
<AlignControlToModule
<AlignToModuleChildSlot
deckId={deckDef.otId}
slotId={module.slotName}
moduleDefinition={module.moduleDef}
Expand All @@ -81,7 +81,7 @@ export const SetupModulesMap = ({
physicalPort={parseModuleUSBPort(module.attachedModuleMatch)}
runId={runId}
/>
</AlignControlToModule>
</AlignToModuleChildSlot>
),
})
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { useMemo } from 'react'

import { BaseDeck, Flex, LabwareRender } from '@opentrons/components'
import {
BaseDeck,
CenterLabwareInSlot,
Flex,
LabwareRender,
} from '@opentrons/components'
import {
FLEX_ROBOT_TYPE,
getLabwareDefinitionsByURIForProtocol,
Expand Down Expand Up @@ -52,6 +57,8 @@ export function LabwareMapView(props: LabwareMapViewProps): JSX.Element {
: null
// TODO: ja 8.27.25: find a better way to find the matching lid def without
// relying on the lidDisplayNames
// TODO: mm 12.3.25: deduplicate with other places where we're doing the same thing
// (grep for matchingLidDef)
const matchingLidDef = Object.values(definitionsByURI).find(
uri => uri.metadata.displayName === topLabwareInfo?.lidDisplayName
)
Expand Down Expand Up @@ -97,6 +104,10 @@ export function LabwareMapView(props: LabwareMapViewProps): JSX.Element {
topLabwareInfo != null
? definitionsByURI[topLabwareInfo.definitionUri]
: null
// TODO: ja 8.27.25: find a better way to find the matching lid def without
// relying on the lidDisplayNames
// TODO: mm 12.3.25: deduplicate with other places where we're doing the same thing
// (grep for matchingLidDef)
const matchingLidDef = Object.values(definitionsByURI).find(
uri => uri.metadata.displayName === topLabwareInfo?.lidDisplayName
)
Expand All @@ -116,15 +127,17 @@ export function LabwareMapView(props: LabwareMapViewProps): JSX.Element {
onLabwareClick: () => {
handleLabwareClick([slotName, stackedItems])
},
wellFill: wellFill,
wellFill,
highlight: true,
stacked: isLabwareInStack,
labwareChildren:
matchingLidDef != null ? (
<LabwareRender
definition={matchingLidDef}
positioningMode="passThrough"
/>
<CenterLabwareInSlot definition={matchingLidDef}>
<LabwareRender
definition={matchingLidDef}
positioningMode="passThrough"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need positioning modes anymore? what do they do at this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do, but only for now.

<LabwareRender> and <Module> had some intrinsic transforms that made the components difficult to compose. "passThrough" turns those transforms off so another component can do them instead; "offsetInSlot" and "offsetToSlot" preserve the old behavior. Soon (🔜) everything will use "passThrough" and then we can delete the old behavior, but we haven't quite gotten there yet. Just a matter of going through each call site and understanding what it's trying to do.

/>
</CenterLabwareInSlot>
) : null,
}
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AlignControlToModule, BaseDeck, Flex } from '@opentrons/components'
import { AlignToModuleChildSlot, BaseDeck, Flex } from '@opentrons/components'
import {
FLEX_ROBOT_TYPE,
getDeckDefFromRobotType,
Expand Down Expand Up @@ -37,7 +37,7 @@ export function ModulesAndDeckMapView({
moduleModel: module.moduleDef.model,
moduleLocation: { slotName: module.slotName },
moduleChildren: (
<AlignControlToModule
<AlignToModuleChildSlot
deckId={deckDef.otId}
slotId={module.slotName}
moduleDefinition={module.moduleDef}
Expand All @@ -48,7 +48,7 @@ export function ModulesAndDeckMapView({
physicalPort={parseModuleUSBPort(module.attachedModuleMatch)}
runId={runId}
/>
</AlignControlToModule>
</AlignToModuleChildSlot>
),
})
)
Expand Down
Loading
Loading