@@ -3,19 +3,17 @@ import { animated, easings, useSpring } from '@react-spring/web'
3
3
import styled from 'styled-components'
4
4
5
5
import {
6
+ computeLabwareOrigin ,
6
7
getDeckDefFromRobotType ,
7
- getModuleDef ,
8
- getPositionFromSlotId ,
9
- getSchema2CornerOffsetFromSlot ,
10
- getSchema2Dimensions ,
8
+ getLabwareViewBox ,
11
9
} from '@opentrons/shared-data'
12
10
13
11
import { COLORS } from '../../helix-design-system'
14
12
import { BaseDeck } from '../BaseDeck'
15
13
import { LabwareRender } from '../Labware'
16
- import { IDENTITY_AFFINE_TRANSFORM , multiplyMatrices } from '../utils '
14
+ import { resolveLabwareLocation } from './resolveLabwareLocation '
17
15
18
- import type { ReactNode } from 'react'
16
+ import type { PropsWithChildren , ReactNode } from 'react'
19
17
import type {
20
18
DeckConfiguration ,
21
19
DeckDefinition ,
@@ -28,109 +26,6 @@ import type {
28
26
} from '@opentrons/shared-data'
29
27
import type { StyleProps } from '../../primitives'
30
28
31
- const getModulePosition = (
32
- deckDef : DeckDefinition ,
33
- moduleId : string ,
34
- loadedModules : LoadedModule [ ]
35
- ) : Vector3D | null => {
36
- const loadedModule = loadedModules . find ( m => m . id === moduleId )
37
- if ( loadedModule == null ) return null
38
- const modSlot = deckDef . locations . addressableAreas . find (
39
- s => s . id === loadedModule . location . slotName
40
- )
41
- if ( modSlot == null ) return null
42
-
43
- const modPosition = getPositionFromSlotId ( modSlot . id , deckDef )
44
- if ( modPosition == null ) return null
45
- const [ modX , modY ] = modPosition
46
-
47
- const deckSpecificAffineTransform =
48
- getModuleDef ( loadedModule . model ) . slotTransforms ?. [ deckDef . otId ] ?. [
49
- modSlot . id
50
- ] ?. labwareOffset ?? IDENTITY_AFFINE_TRANSFORM
51
- const [ [ labwareX ] , [ labwareY ] , [ labwareZ ] ] = multiplyMatrices (
52
- [ [ modX ] , [ modY ] , [ 1 ] , [ 1 ] ] ,
53
- deckSpecificAffineTransform
54
- )
55
- return { x : labwareX , y : labwareY , z : labwareZ }
56
- }
57
-
58
- function getLabwareCoordinates ( {
59
- deckDef,
60
- location,
61
- loadedModules,
62
- loadedLabware,
63
- } : {
64
- deckDef : DeckDefinition
65
- location : LabwareLocation
66
- loadedModules : LoadedModule [ ]
67
- loadedLabware : LoadedLabware [ ]
68
- } ) : Vector3D | null {
69
- if ( location === 'offDeck' || location === 'systemLocation' ) {
70
- return null
71
- } else if ( 'labwareId' in location ) {
72
- const loadedAdapter = loadedLabware . find ( l => l . id === location . labwareId )
73
- if ( loadedAdapter == null ) return null
74
- const loadedAdapterLocation = loadedAdapter . location
75
-
76
- if (
77
- loadedAdapterLocation === 'offDeck' ||
78
- loadedAdapterLocation === 'systemLocation' ||
79
- 'labwareId' in loadedAdapterLocation
80
- )
81
- return null
82
- // adapter on module
83
- if ( 'moduleId' in loadedAdapterLocation ) {
84
- return getModulePosition (
85
- deckDef ,
86
- loadedAdapterLocation . moduleId ,
87
- loadedModules
88
- )
89
- }
90
-
91
- // adapter on deck
92
- const loadedAdapterSlotPosition = getPositionFromSlotId (
93
- 'slotName' in loadedAdapterLocation
94
- ? loadedAdapterLocation . slotName
95
- : loadedAdapterLocation . addressableAreaName ,
96
- deckDef
97
- )
98
- return loadedAdapterSlotPosition != null
99
- ? {
100
- x : loadedAdapterSlotPosition [ 0 ] ,
101
- y : loadedAdapterSlotPosition [ 1 ] ,
102
- z : loadedAdapterSlotPosition [ 2 ] ,
103
- }
104
- : null
105
- } else if ( 'addressableAreaName' in location ) {
106
- const slotCoordinateTuple = getPositionFromSlotId (
107
- location . addressableAreaName ,
108
- deckDef
109
- )
110
- return slotCoordinateTuple != null
111
- ? {
112
- x : slotCoordinateTuple [ 0 ] ,
113
- y : slotCoordinateTuple [ 1 ] ,
114
- z : slotCoordinateTuple [ 2 ] ,
115
- }
116
- : null
117
- } else if ( 'slotName' in location ) {
118
- const slotCoordinateTuple = getPositionFromSlotId (
119
- location . slotName ,
120
- deckDef
121
- )
122
- return slotCoordinateTuple != null
123
- ? {
124
- x : slotCoordinateTuple [ 0 ] ,
125
- y : slotCoordinateTuple [ 1 ] ,
126
- z : slotCoordinateTuple [ 2 ] ,
127
- }
128
- : null
129
- } else {
130
- return getModulePosition ( deckDef , location . moduleId , loadedModules )
131
- }
132
- }
133
-
134
29
const SPLASH_Y_BUFFER_MM = 10
135
30
136
31
interface MoveLabwareOnDeckProps extends StyleProps {
@@ -140,6 +35,7 @@ interface MoveLabwareOnDeckProps extends StyleProps {
140
35
finalLabwareLocation : LabwareLocation
141
36
loadedModules : LoadedModule [ ]
142
37
loadedLabware : LoadedLabware [ ]
38
+ labwareDefinitions : LabwareDefinition [ ]
143
39
deckConfig : DeckConfiguration
144
40
backgroundItems ?: ReactNode
145
41
deckFill ?: string
@@ -151,6 +47,7 @@ export function MoveLabwareOnDeck(
151
47
robotType,
152
48
movedLabwareDef,
153
49
loadedLabware,
50
+ labwareDefinitions,
154
51
initialLabwareLocation,
155
52
finalLabwareLocation,
156
53
loadedModules,
@@ -160,69 +57,82 @@ export function MoveLabwareOnDeck(
160
57
} = props
161
58
const deckDef = useMemo ( ( ) => getDeckDefFromRobotType ( robotType ) , [ robotType ] )
162
59
163
- const initialSlotId =
164
- initialLabwareLocation === 'offDeck' ||
165
- initialLabwareLocation === 'systemLocation' ||
166
- ! ( 'slotName' in initialLabwareLocation )
167
- ? deckDef . locations . addressableAreas [ 1 ] . id
168
- : initialLabwareLocation . slotName
169
-
170
- const slotPosition = getPositionFromSlotId ( initialSlotId , deckDef ) ?? [
171
- 0 ,
172
- 0 ,
173
- 0 ,
174
- ]
175
-
176
- const cornerOffsetFromSlot = getSchema2CornerOffsetFromSlot ( movedLabwareDef )
60
+ const initialResolvedLocation = resolveLabwareLocation ( {
61
+ deckDef,
62
+ targetLabwareDef : movedLabwareDef ,
63
+ targetLabwareLocation : initialLabwareLocation ,
64
+ loadedModules,
65
+ otherLoadedLabware : loadedLabware ,
66
+ otherLabwareDefinitions : labwareDefinitions ,
67
+ } )
68
+ const finalResolvedLocation = resolveLabwareLocation ( {
69
+ deckDef,
70
+ targetLabwareDef : movedLabwareDef ,
71
+ targetLabwareLocation : finalLabwareLocation ,
72
+ loadedModules,
73
+ otherLoadedLabware : loadedLabware ,
74
+ otherLabwareDefinitions : labwareDefinitions ,
75
+ } )
177
76
178
- const offDeckPosition = {
179
- x : slotPosition [ 0 ] ,
180
- y :
181
- deckDef . cornerOffsetFromOrigin [ 1 ] -
182
- getSchema2Dimensions ( movedLabwareDef ) . xDimension -
183
- SPLASH_Y_BUFFER_MM ,
184
- }
185
- const initialPosition =
186
- getLabwareCoordinates ( {
187
- deckDef,
188
- location : initialLabwareLocation ,
189
- loadedModules,
190
- loadedLabware,
191
- } ) ?? offDeckPosition
192
- const finalPosition =
193
- getLabwareCoordinates ( {
194
- deckDef,
195
- location : finalLabwareLocation ,
196
- loadedModules,
197
- loadedLabware,
198
- } ) ?? offDeckPosition
77
+ const initialCoordinates =
78
+ initialResolvedLocation === 'error' || initialResolvedLocation === 'offDeck'
79
+ ? initialResolvedLocation
80
+ : computeLabwareOrigin ( initialResolvedLocation ) ?? 'error'
81
+ const finalCoordinates =
82
+ finalResolvedLocation === 'error' || finalResolvedLocation === 'offDeck'
83
+ ? finalResolvedLocation
84
+ : computeLabwareOrigin ( finalResolvedLocation ) ?? 'error'
85
+
86
+ const referenceForOffDeckCoordinates = ( ( ) => {
87
+ if ( initialCoordinates !== 'error' && initialCoordinates !== 'offDeck' ) {
88
+ return initialCoordinates
89
+ } else if ( finalCoordinates !== 'error' && finalCoordinates !== 'offDeck' ) {
90
+ return finalCoordinates
91
+ } else {
92
+ return { x : 0 , y : 0 , z : 0 }
93
+ }
94
+ } ) ( )
95
+ const offDeckCoordinates = getOffDeckCoordinates (
96
+ deckDef ,
97
+ movedLabwareDef ,
98
+ referenceForOffDeckCoordinates ,
99
+ SPLASH_Y_BUFFER_MM
100
+ )
199
101
200
- const shouldReset = usePositionChangeReset ( initialPosition , finalPosition )
102
+ const animationInitialCoordinates =
103
+ initialCoordinates !== 'error' && initialCoordinates !== 'offDeck'
104
+ ? initialCoordinates
105
+ : offDeckCoordinates
106
+ const animationFinalCoordinates =
107
+ finalCoordinates !== 'error' && finalCoordinates !== 'offDeck'
108
+ ? finalCoordinates
109
+ : offDeckCoordinates
110
+
111
+ const shouldReset = usePositionChangeReset (
112
+ animationInitialCoordinates ,
113
+ animationFinalCoordinates
114
+ )
201
115
202
116
const springProps = useSpring ( {
203
117
reset : shouldReset ,
204
118
config : { duration : 1000 , easing : easings . easeInOutSine } ,
205
119
from : {
206
- ...initialPosition ,
120
+ ...animationInitialCoordinates ,
207
121
splashOpacity : 0 ,
208
122
deckOpacity : 0 ,
209
123
} ,
210
124
to : [
211
125
{ deckOpacity : 1 } ,
212
126
{ splashOpacity : 1 } ,
213
127
{ splashOpacity : 0 } ,
214
- { ...finalPosition } ,
128
+ { ...animationFinalCoordinates } ,
215
129
{ splashOpacity : 1 } ,
216
130
{ splashOpacity : 0 } ,
217
131
{ deckOpacity : 0 } ,
218
132
] ,
219
133
loop : true ,
220
134
} )
221
135
222
- if ( deckDef == null ) {
223
- return null
224
- }
225
-
226
136
return (
227
137
< BaseDeck
228
138
deckConfig = { deckConfig }
@@ -235,25 +145,52 @@ export function MoveLabwareOnDeck(
235
145
>
236
146
{ backgroundItems }
237
147
< AnimatedG style = { { x : springProps . x , y : springProps . y } } >
238
- < g
239
- transform = { `translate(${ cornerOffsetFromSlot . x } , ${ cornerOffsetFromSlot . y } )` }
240
- >
241
- < LabwareRender definition = { movedLabwareDef } highlight = { true } />
242
- < AnimatedG style = { { opacity : springProps . splashOpacity } } >
148
+ < LabwareRender
149
+ definition = { movedLabwareDef }
150
+ positioningMode = "passThrough"
151
+ highlight = { true }
152
+ />
153
+ < AnimatedG style = { { opacity : springProps . splashOpacity } } >
154
+ < AlignSplashToLabware labwareDefinition = { movedLabwareDef } >
243
155
< path
244
156
d = "M158.027 111.537L154.651 108.186M145.875 113L145.875 109.253M161 99.3038L156.864 99.3038M11.9733 10.461L15.3495 13.8128M24.1255 9L24.1254 12.747M9 22.6962L13.1357 22.6962"
245
157
stroke = { COLORS . blue50 }
246
158
strokeWidth = "3.57"
247
159
strokeLinecap = "round"
248
160
transform = "scale(.97, -1) translate(-19, -104)"
249
161
/>
250
- </ AnimatedG >
251
- </ g >
162
+ </ AlignSplashToLabware >
163
+ </ AnimatedG >
252
164
</ AnimatedG >
253
165
</ BaseDeck >
254
166
)
255
167
}
256
168
169
+ /**
170
+ * Returns the coordinates of a location beyond the bounds of the deck.
171
+ *
172
+ * @param onDeckCoordinates - The coordinates of the labware when it's on-deck. The
173
+ * off-deck location is chosen to be vertically aligned with this, so the animation
174
+ * goes straight up or down.
175
+ */
176
+ function getOffDeckCoordinates (
177
+ deckDefinition : DeckDefinition ,
178
+ labwareDefinition : LabwareDefinition ,
179
+ onDeckCoordinates : Vector3D ,
180
+ extraMargin : number
181
+ ) : Vector3D {
182
+ const labwareViewBox = getLabwareViewBox ( labwareDefinition )
183
+ const labwareOriginToLabwareMaxY =
184
+ labwareViewBox . minY + labwareViewBox . yDimension
185
+ const margin = labwareOriginToLabwareMaxY + extraMargin
186
+ const deckMinY = deckDefinition . cornerOffsetFromOrigin [ 1 ]
187
+ const y = deckMinY - margin - labwareOriginToLabwareMaxY
188
+ return {
189
+ ...onDeckCoordinates ,
190
+ y,
191
+ }
192
+ }
193
+
257
194
function usePositionChangeReset (
258
195
initialPosition : { x : number ; y : number } ,
259
196
finalPosition : { x : number ; y : number }
@@ -285,6 +222,31 @@ function usePositionChangeReset(
285
222
286
223
return shouldReset
287
224
}
225
+
226
+ /**
227
+ * The splash SVG is made for its origin to be placed at the -x,-y corner of a labware
228
+ * (or the slot that the labware is in). If this component is placed at the labware
229
+ * origin and the splash is placed inside this component, it will align the splash
230
+ * accordingly.
231
+ */
232
+ function AlignSplashToLabware (
233
+ props : PropsWithChildren < { labwareDefinition : LabwareDefinition } >
234
+ ) : JSX . Element {
235
+ const { labwareDefinition, children } = props
236
+ const labwareViewBox = getLabwareViewBox ( labwareDefinition )
237
+ const labwareOriginToFrontLeftCorner = {
238
+ x : labwareViewBox . minX ,
239
+ y : labwareViewBox . minY ,
240
+ }
241
+ return (
242
+ < g
243
+ transform = { `translate(${ labwareOriginToFrontLeftCorner . x } ${ labwareOriginToFrontLeftCorner . y } )` }
244
+ >
245
+ { children }
246
+ </ g >
247
+ )
248
+ }
249
+
288
250
/**
289
251
* These animated components needs to be split out because react-spring and styled-components don't play nice
290
252
* @see https://github.com/pmndrs/react-spring/issues/1515 */
0 commit comments