Skip to content

Commit 831b80e

Browse files
refactor(app,components): Simplifications to CalibrationBlockRender (#18548)
## Overview Some refactors and light fixes to simplify the `CalibrationBlockRender` component. This component is used in OT-2 calibration flows to render a top-down view of the calibration block. Helps with EXEC-1573. ## Changelog Notable changes: * Move the component from `app` to `components/hardware-sim`. This lets us make a Storybook story for it, and it lets us keep it next to the spiritually similar `LabwareAdapter` component. * Simplify the way we position the rotated "SHORT" and "TALL" labels. Formerly, they were positioned in outer space, and then an SVG `rotate()` transform swung them in a wide arc around the labware origin to land in their final position. So you had to choose the outer-space coordinates carefully, and you'd have to reason through things like x- and y-coordinates being swapped. Now, we position the text where we want it and rotate it in-place. * Exactly vertically align the middle of the "SHORT" and "TALL" labels to the middle of the labware. The former code was positioning the labels by their edge and seemingly trying to align them by hard-coded offsets, which was visibly off-center once you noticed it.
1 parent cb51d1b commit 831b80e

File tree

4 files changed

+241
-142
lines changed

4 files changed

+241
-142
lines changed
Lines changed: 23 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import {
2-
C_MED_DARK_GRAY,
3-
C_MED_GRAY,
4-
C_MED_LIGHT_GRAY,
5-
FONT_WEIGHT_SEMIBOLD,
2+
CalibrationBlockRender,
63
LabwareNameOverlay,
74
LabwareRender,
85
RobotCoordsForeignDiv,
9-
RobotCoordsText,
10-
TYPOGRAPHY,
116
} from '@opentrons/components'
127
import {
138
getIsTiprack,
@@ -19,9 +14,6 @@ import styles from './styles.module.css'
1914

2015
import type { CoordinateTuple, LabwareDefinition } from '@opentrons/shared-data'
2116

22-
const SHORT = 'SHORT'
23-
const TALL = 'TALL'
24-
2517
interface CalibrationLabwareRenderProps {
2618
labwareDef: LabwareDefinition
2719
slotDefPosition: CoordinateTuple | null
@@ -36,144 +28,33 @@ export function CalibrationLabwareRender(
3628
const dimensions = getSchema2Dimensions(labwareDef)
3729
const isTiprack = getIsTiprack(labwareDef)
3830

39-
// TODO: we can change this boolean to check to isCalibrationBlock instead of isTiprack to render any labware
40-
return isTiprack ? (
31+
return (
4132
<g
4233
transform={`translate(${String(slotDefPosition?.[0])}, ${String(
4334
slotDefPosition?.[1]
4435
)})`}
4536
>
46-
<LabwareRender definition={labwareDef} />
47-
<RobotCoordsForeignDiv
48-
width={dimensions.xDimension}
49-
height={dimensions.yDimension}
50-
x={0}
51-
y={0 - dimensions.yDimension}
52-
transformWithSVG
53-
innerDivProps={{ className: styles.labware_ui_wrapper }}
54-
>
55-
{/* title is capitalized by CSS, and "µL" capitalized is "ML" */}
56-
<LabwareNameOverlay title={title.replace('µL', 'uL')} />
57-
</RobotCoordsForeignDiv>
37+
{
38+
// TODO: we can change this boolean to check to isCalibrationBlock instead of isTiprack to render any labware
39+
isTiprack ? (
40+
<>
41+
<LabwareRender definition={labwareDef} />
42+
<RobotCoordsForeignDiv
43+
width={dimensions.xDimension}
44+
height={dimensions.yDimension}
45+
x={0}
46+
y={0 - dimensions.yDimension}
47+
transformWithSVG
48+
innerDivProps={{ className: styles.labware_ui_wrapper }}
49+
>
50+
{/* title is capitalized by CSS, and "µL" capitalized is "ML" */}
51+
<LabwareNameOverlay title={title.replace('µL', 'uL')} />
52+
</RobotCoordsForeignDiv>
53+
</>
54+
) : (
55+
<CalibrationBlockRender labwareDef={labwareDef} />
56+
)
57+
}
5858
</g>
59-
) : (
60-
<CalibrationBlockRender
61-
labwareDef={labwareDef}
62-
slotDefPosition={slotDefPosition}
63-
/>
6459
)
6560
}
66-
67-
export function CalibrationBlockRender(
68-
props: CalibrationLabwareRenderProps
69-
): JSX.Element | null {
70-
const { labwareDef, slotDefPosition } = props
71-
const dimensions = getSchema2Dimensions(labwareDef)
72-
73-
switch (labwareDef.parameters.loadName) {
74-
case 'opentrons_calibrationblock_short_side_right': {
75-
return (
76-
<g
77-
transform={`translate(${String(slotDefPosition?.[0])}, ${String(
78-
slotDefPosition?.[1]
79-
)})`}
80-
>
81-
<rect
82-
width={dimensions.xDimension}
83-
height={dimensions.yDimension}
84-
rx="10"
85-
ry="10"
86-
x={0}
87-
y={0}
88-
fill={C_MED_DARK_GRAY}
89-
/>
90-
<rect
91-
width={dimensions.xDimension / 2}
92-
height={dimensions.yDimension}
93-
rx="10"
94-
ry="10"
95-
x={0}
96-
y={0}
97-
fill={C_MED_GRAY}
98-
/>
99-
<g transform="rotate(270)">
100-
<RobotCoordsText
101-
x={-55}
102-
y={5}
103-
fill={C_MED_LIGHT_GRAY}
104-
fontSize={TYPOGRAPHY.fontSizeCaption}
105-
fontWeight={FONT_WEIGHT_SEMIBOLD}
106-
>
107-
{TALL}
108-
</RobotCoordsText>
109-
</g>
110-
<g transform="rotate(90)">
111-
<RobotCoordsText
112-
x={25}
113-
y={-dimensions.xDimension + 5}
114-
fill={C_MED_LIGHT_GRAY}
115-
fontSize={TYPOGRAPHY.fontSizeCaption}
116-
fontWeight={FONT_WEIGHT_SEMIBOLD}
117-
>
118-
{SHORT}
119-
</RobotCoordsText>
120-
</g>
121-
</g>
122-
)
123-
}
124-
case 'opentrons_calibrationblock_short_side_left': {
125-
return (
126-
<g
127-
transform={`translate(${String(slotDefPosition?.[0])}, ${String(
128-
slotDefPosition?.[1]
129-
)})`}
130-
>
131-
<rect
132-
width={dimensions.xDimension}
133-
height={dimensions.yDimension}
134-
rx="10"
135-
ry="10"
136-
x={0}
137-
y={0}
138-
fill={C_MED_DARK_GRAY}
139-
/>
140-
<rect
141-
width={dimensions.xDimension / 2}
142-
height={dimensions.yDimension}
143-
rx="10"
144-
ry="10"
145-
x={dimensions.xDimension / 2}
146-
y={0}
147-
fill={C_MED_GRAY}
148-
/>
149-
<g transform="rotate(270)">
150-
<RobotCoordsText
151-
x={-55}
152-
y={5}
153-
fill={C_MED_LIGHT_GRAY}
154-
fontSize={TYPOGRAPHY.fontSizeCaption}
155-
fontWeight={FONT_WEIGHT_SEMIBOLD}
156-
>
157-
{SHORT}
158-
</RobotCoordsText>
159-
</g>
160-
<g transform="rotate(90)">
161-
<RobotCoordsText
162-
x={30}
163-
y={-dimensions.xDimension + 5}
164-
fill={C_MED_LIGHT_GRAY}
165-
fontSize={TYPOGRAPHY.fontSizeCaption}
166-
fontWeight={FONT_WEIGHT_SEMIBOLD}
167-
>
168-
{TALL}
169-
</RobotCoordsText>
170-
</g>
171-
</g>
172-
)
173-
}
174-
default: {
175-
// should never reach this case
176-
return null
177-
}
178-
}
179-
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { getAllDefinitions, getSchema2Dimensions } from '@opentrons/shared-data'
2+
3+
import { RobotWorkSpace } from '../Deck'
4+
import { CalibrationBlockRender } from './CalibrationBlockRender'
5+
6+
import type { Meta, StoryObj } from '@storybook/react'
7+
8+
const LOAD_NAMES = [
9+
'opentrons_calibrationblock_short_side_left',
10+
'opentrons_calibrationblock_short_side_right',
11+
]
12+
const DEFS_BY_URI = Object.fromEntries(
13+
Object.entries(getAllDefinitions()).filter(([uri, def]) =>
14+
LOAD_NAMES.includes(def.parameters.loadName)
15+
)
16+
)
17+
18+
const meta: Meta<typeof CalibrationBlockRender> = {
19+
title: 'Library/Molecules/Simulation/CalibrationBlockRender',
20+
component: CalibrationBlockRender,
21+
decorators: [
22+
(Story, context) => {
23+
const { labwareDef } = context.args
24+
// todo(mm, 2025-06-05): Update viewBox to account for labware schema 3.
25+
const { xDimension, yDimension } = getSchema2Dimensions(labwareDef)
26+
const viewBox = `0 0 ${xDimension} ${yDimension}`
27+
28+
return (
29+
<RobotWorkSpace viewBox={viewBox}>{() => <Story />}</RobotWorkSpace>
30+
)
31+
},
32+
],
33+
}
34+
35+
export default meta
36+
type Story = StoryObj<typeof meta>
37+
38+
export const Primary: Story = {
39+
args: {
40+
labwareDef: Object.keys(DEFS_BY_URI)[0],
41+
},
42+
argTypes: {
43+
labwareDef: {
44+
options: Object.keys(DEFS_BY_URI),
45+
mapping: DEFS_BY_URI,
46+
},
47+
},
48+
}

0 commit comments

Comments
 (0)