Skip to content

Commit 3159f50

Browse files
Port getRunLabwareRenderInfo() and its couple of call sites.
1 parent 5360a40 commit 3159f50

File tree

5 files changed

+155
-24
lines changed

5 files changed

+155
-24
lines changed

app/src/organisms/ErrorRecoveryFlows/shared/TwoColLwInfoAndDeck.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,16 @@ export function TwoColLwInfoAndDeck(
243243
)}
244244
{labwareRenderInfo
245245
.filter(l => l.labwareId !== failedLwId)
246-
.map(({ x, y, labwareDef, labwareId }) => (
247-
<g key={labwareId} transform={`translate(${x},${y})`}>
246+
.map(({ labwareOrigin, labwareDef, labwareId }) => (
247+
<g
248+
key={labwareId}
249+
transform={`translate(${labwareOrigin.x},${labwareOrigin.y})`}
250+
>
248251
{labwareDef != null && labwareId !== failedLwId ? (
249-
<LabwareRender definition={labwareDef} />
252+
<LabwareRender
253+
definition={labwareDef}
254+
positioningMode="passThrough"
255+
/>
250256
) : null}
251257
</g>
252258
))}

app/src/organisms/InterventionModal/MoveLabwareInterventionContent.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,17 @@ export function MoveLabwareInterventionContent({
227227
)}
228228
{labwareRenderInfo
229229
.filter(l => l.labwareId !== command.params.labwareId)
230-
.map(({ x, y, labwareDef, labwareId }) => (
231-
<g key={labwareId} transform={`translate(${x},${y})`}>
230+
.map(({ labwareOrigin, labwareDef, labwareId }) => (
231+
<g
232+
key={labwareId}
233+
transform={`translate(${labwareOrigin.x},${labwareOrigin.y})`}
234+
>
232235
{labwareDef != null &&
233236
labwareId !== command.params.labwareId ? (
234-
<LabwareRender definition={labwareDef} />
237+
<LabwareRender
238+
definition={labwareDef}
239+
positioningMode="passThrough"
240+
/>
235241
) : null}
236242
</g>
237243
))}

app/src/organisms/InterventionModal/utils/getRunLabwareRenderInfo.ts

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
import {
2+
coordinateTupleToVector3D,
3+
getAddressableAreaFromSlotId,
4+
getDeckSlotOriginToLabwareOrigin,
5+
getLabwareBackLeftBottomToOrigin,
26
getPositionFromSlotId,
3-
getSchema2Dimensions,
47
getSlotHasMatingSurfaceUnitVector,
8+
getVectorSum,
59
} from '@opentrons/shared-data'
610

711
import type { RunData } from '@opentrons/api-client'
812
import type {
913
DeckDefinition,
1014
LabwareDefinition,
1115
LabwareDefinitionsByUri,
16+
Vector3D,
1217
} from '@opentrons/shared-data'
1318

1419
export interface RunLabwareInfo {
15-
x: number
16-
y: number
20+
/**
21+
* The labware origin, in deck coordinates.
22+
* Use this with `<LabwareRender positioningMode="passThrough">`.
23+
*/
24+
labwareOrigin: Vector3D
25+
1726
labwareDef: LabwareDefinition
1827
labwareId: string
1928
}
@@ -44,30 +53,72 @@ export function getRunLabwareRenderInfo(
4453
'addressableAreaName' in location
4554
? location.addressableAreaName
4655
: location.slotName
47-
const slotPosition = getPositionFromSlotId(slotName, deckDef)
56+
57+
const slotAddressableArea = getAddressableAreaFromSlotId(
58+
slotName,
59+
deckDef
60+
)
61+
if (slotAddressableArea == null) {
62+
return acc
63+
}
64+
4865
const slotHasMatingSurfaceVector = getSlotHasMatingSurfaceUnitVector(
4966
deckDef,
5067
slotName
5168
)
69+
if (!slotHasMatingSurfaceVector) {
70+
// todo(mm, 2025-06-25): Are we using slotHasMatingSurfaceVector as an
71+
// approximation for "is a deck slot"? Should we use
72+
// `slotAddressableArea.areaType === "slot"`` instead?
73+
return acc
74+
}
75+
76+
// 0,0,0 default inherited from prior code. I don't think we ever reach it in
77+
// practice, just keeping it to be safe.
78+
const slotOriginTuple = getPositionFromSlotId(slotName, deckDef) ?? [
79+
0,
80+
0,
81+
0,
82+
]
83+
const slotOrigin = coordinateTupleToVector3D(slotOriginTuple)
84+
85+
const slotOriginToLabwareOrigin = getDeckSlotOriginToLabwareOrigin(
86+
slotAddressableArea,
87+
labwareDef
88+
)
89+
const labwareOrigin = getVectorSum(
90+
slotOrigin,
91+
slotOriginToLabwareOrigin
92+
)
5293

53-
return slotHasMatingSurfaceVector
54-
? [
55-
...acc,
56-
{
57-
x: slotPosition?.[0] ?? 0,
58-
y: slotPosition?.[1] ?? 0,
59-
labwareId: labware.id,
60-
labwareDef,
61-
},
62-
]
63-
: acc
94+
return [
95+
...acc,
96+
{
97+
labwareOrigin,
98+
labwareId: labware.id,
99+
labwareDef,
100+
},
101+
]
64102
} else {
65-
const { yDimension } = getSchema2Dimensions(labwareDef)
103+
// Place off-deck labware literally beyond the bounds of the deck.
104+
// Calling code can change the position for animated fly-ins/fly-outs.
105+
// It's unclear to me whether it matters exactly what position we choose here;
106+
// this roughly maintains prior behavior.
107+
const whereToPutLabwareBackLeftBottom: Vector3D = {
108+
x: 0,
109+
y: deckDef.cornerOffsetFromOrigin[1],
110+
z: 0,
111+
}
112+
113+
const labwareOrigin = getVectorSum(
114+
whereToPutLabwareBackLeftBottom,
115+
getLabwareBackLeftBottomToOrigin(labwareDef)
116+
)
117+
66118
return [
67119
...acc,
68120
{
69-
x: 0,
70-
y: deckDef.cornerOffsetFromOrigin[1] - yDimension,
121+
labwareOrigin,
71122
labwareId: labware.id,
72123
labwareDef,
73124
},

shared-data/js/helpers/__tests__/labwareSchemaShims.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
22

33
import {
44
getDeckSlotOriginToLabwareOrigin,
5+
getLabwareBackLeftBottomToOrigin,
56
getLabwareViewBox,
67
getSchema2Dimensions,
78
} from '../..'
@@ -171,3 +172,47 @@ describe('getDeckSlotOriginToLabwareOrigin()', () => {
171172
expect(result).toStrictEqual(expectedResult)
172173
})
173174
})
175+
176+
describe('getLabwareBackLeftBottomToOrigin()', () => {
177+
it('should handle schema 2 labware definitions', () => {
178+
const labwareDef: Partial<LabwareDefinition2> = {
179+
schemaVersion: 2,
180+
cornerOffsetFromSlot: {
181+
// Should not affect result.
182+
x: 10,
183+
y: 20,
184+
z: 30,
185+
},
186+
dimensions: {
187+
xDimension: 100,
188+
yDimension: 200,
189+
zDimension: 300,
190+
},
191+
}
192+
const result = getLabwareBackLeftBottomToOrigin(
193+
labwareDef as LabwareDefinition2
194+
)
195+
const expectedResult: typeof result = { x: 0, y: -200, z: 0 }
196+
expect(result).toStrictEqual(expectedResult)
197+
})
198+
it('should handle schema 3 labware definitions', () => {
199+
const labwareDef: Partial<LabwareDefinition3> = {
200+
schemaVersion: 3,
201+
extents: {
202+
total: {
203+
backLeftBottom: { x: -10, y: 10, z: 0 },
204+
frontRightTop: { x: 210, y: -110, z: 1000 },
205+
},
206+
},
207+
}
208+
const result = getLabwareBackLeftBottomToOrigin(
209+
labwareDef as LabwareDefinition3
210+
)
211+
const expectedResult: typeof result = {
212+
x: 10,
213+
y: -10,
214+
z: -0,
215+
}
216+
expect(result).toStrictEqual(expectedResult)
217+
})
218+
})

shared-data/js/helpers/labwareSchemaShims.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,29 @@ export function getDeckSlotOriginToLabwareOrigin(
112112
}
113113
}
114114

115+
/**
116+
* Return the offset from a labware's back-left-bottom (-x, +y, -z) corner to its origin.
117+
*
118+
* This is a lower-level helper for the rare cases where we want to position a labware
119+
* specifically by its back-left corner. Typically, you'll want something higher-level,
120+
* like `getLabwareViewBox()` or `getDeckSlotOriginToLabwareOrigin()`, instead.
121+
*/
122+
export function getLabwareBackLeftBottomToOrigin(
123+
definition: LabwareDefinition
124+
): Vector3D {
125+
if (definition.schemaVersion === 2) {
126+
return {
127+
x: 0,
128+
y: -definition.dimensions.yDimension,
129+
z: 0,
130+
}
131+
} else {
132+
const originToBackLeftBottom = definition.extents.total.backLeftBottom
133+
const backLeftBottomToOrigin = getVectorInverse(originToBackLeftBottom)
134+
return backLeftBottomToOrigin
135+
}
136+
}
137+
115138
/**
116139
* Return a definition's cornerOffsetFromSlot in the style of labware schema 2.
117140
*

0 commit comments

Comments
 (0)