Skip to content

Commit ff5563e

Browse files
committed
add support for x and y offsets
1 parent 24b1ae0 commit ff5563e

File tree

4 files changed

+94
-40
lines changed

4 files changed

+94
-40
lines changed

protocol-designer/src/timelineMiddleware/__tests__/generateRobotStateTimeline.test.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -208,16 +208,10 @@ mockPythonName.drop_tip()
208208
// Step c:
209209
`
210210
mockPythonName.pick_up_tip(location=mockPythonName)
211-
mockPythonName.aspirate(...)
212-
mockPythonName.dispense(...)
213-
mockPythonName.aspirate(...)
214-
mockPythonName.dispense(...)
211+
mockPythonName.mix(...)
215212
mockPythonName.drop_tip()
216213
mockPythonName.pick_up_tip(location=mockPythonName)
217-
mockPythonName.aspirate(...)
218-
mockPythonName.dispense(...)
219-
mockPythonName.aspirate(...)
220-
mockPythonName.dispense(...)
214+
mockPythonName.mix(...)
221215
mockPythonName.drop_tip()
222216
`.trim(),
223217
])

shared-data/command/types/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export * from './pipetting'
3030
export * from './setup'
3131
export * from './timing'
3232
export * from './unsafe'
33-
33+
export * from './support'
3434
// NOTE: these key/value pairs will only be present on commands at analysis/run time
3535
// they pertain only to the actual execution status of a command on hardware, as opposed to
3636
// the command's identity and parameters which can be known prior to runtime

step-generation/src/__tests__/mix.test.ts

+62-23
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,28 @@ describe('mix: change tip', () => {
104104
`
105105
mockPythonName.drop_tip()
106106
mockPythonName.pick_up_tip(location=mockPythonName)
107-
mockPythonName.mix(2, 5, mockPythonName["A1"])
107+
mockPythonName.mix(
108+
repetitions=2,
109+
volume=5,
110+
location=mockPythonName["A1"].bottom(z=3.2),
111+
rate=(2.1 / mockPythonName.flow_rate.aspirate) + (2.2 / mockPythonName.flow_rate.dispense),
112+
)
108113
mockPythonName.drop_tip()
109114
mockPythonName.pick_up_tip(location=mockPythonName)
110-
mockPythonName.mix(2, 5, mockPythonName["B1"])
115+
mockPythonName.mix(
116+
repetitions=2,
117+
volume=5,
118+
location=mockPythonName["B1"].bottom(z=3.2),
119+
rate=(2.1 / mockPythonName.flow_rate.aspirate) + (2.2 / mockPythonName.flow_rate.dispense),
120+
)
111121
mockPythonName.drop_tip()
112122
mockPythonName.pick_up_tip(location=mockPythonName)
113-
mockPythonName.mix(2, 5, mockPythonName["C1"])`.trimStart()
123+
mockPythonName.mix(
124+
repetitions=2,
125+
volume=5,
126+
location=mockPythonName["C1"].bottom(z=3.2),
127+
rate=(2.1 / mockPythonName.flow_rate.aspirate) + (2.2 / mockPythonName.flow_rate.dispense),
128+
)`.trimStart()
114129
)
115130
})
116131

@@ -326,21 +341,28 @@ describe('mix: advanced options', () => {
326341
times,
327342
changeTip: 'always',
328343
wells: ['A1', 'B1', 'C1'],
344+
yOffset: 1,
329345
} as MixArgs
346+
const mockWellLocationCustomXY: Partial<AspDispAirgapParams> = {
347+
wellLocation: {
348+
origin: 'bottom',
349+
offset: { x: 0, y: 1, z: 3.2 },
350+
},
351+
}
330352

331353
const result = mix(args, invariantContext, robotStateWithTip)
332354
const res = getSuccessResult(result)
333355

334356
expect(res.commands).toEqual(
335357
flatMap(args.wells, (well, idx) => [
336358
...replaceTipCommands(idx),
337-
aspirateHelper(well, volume, mockWellLocation),
359+
aspirateHelper(well, volume, mockWellLocationCustomXY),
338360
delayCommand(10),
339-
dispenseHelper(well, volume, mockWellLocation),
361+
dispenseHelper(well, volume, mockWellLocationCustomXY),
340362
delayCommand(12),
341-
aspirateHelper(well, volume, mockWellLocation),
363+
aspirateHelper(well, volume, mockWellLocationCustomXY),
342364
delayCommand(10),
343-
dispenseHelper(well, volume, mockWellLocation),
365+
dispenseHelper(well, volume, mockWellLocationCustomXY),
344366
delayCommand(12),
345367
blowoutHelper(blowoutLabwareId, {
346368
wellLocation: {
@@ -359,25 +381,25 @@ mockPythonName.drop_tip()
359381
mockPythonName.pick_up_tip(location=mockPythonName)
360382
mockPythonName.aspirate(
361383
volume=8,
362-
location=mockPythonName["A1"].bottom(z=3.2),
384+
location=mockPythonName["A1"].bottom(z=3.2).move(types.Point(y=1)),
363385
rate=2.1 / mockPythonName.flow_rate.aspirate,
364386
)
365387
protocol.delay(seconds=10)
366388
mockPythonName.dispense(
367389
volume=8,
368-
location=mockPythonName["A1"].bottom(z=3.2),
390+
location=mockPythonName["A1"].bottom(z=3.2).move(types.Point(y=1)),
369391
rate=2.2 / mockPythonName.flow_rate.dispense,
370392
)
371393
protocol.delay(seconds=12)
372394
mockPythonName.aspirate(
373395
volume=8,
374-
location=mockPythonName["A1"].bottom(z=3.2),
396+
location=mockPythonName["A1"].bottom(z=3.2).move(types.Point(y=1)),
375397
rate=2.1 / mockPythonName.flow_rate.aspirate,
376398
)
377399
protocol.delay(seconds=10)
378400
mockPythonName.dispense(
379401
volume=8,
380-
location=mockPythonName["A1"].bottom(z=3.2),
402+
location=mockPythonName["A1"].bottom(z=3.2).move(types.Point(y=1)),
381403
rate=2.2 / mockPythonName.flow_rate.dispense,
382404
)
383405
protocol.delay(seconds=12)
@@ -391,25 +413,25 @@ mockPythonName.drop_tip()
391413
mockPythonName.pick_up_tip(location=mockPythonName)
392414
mockPythonName.aspirate(
393415
volume=8,
394-
location=mockPythonName["B1"].bottom(z=3.2),
416+
location=mockPythonName["B1"].bottom(z=3.2).move(types.Point(y=1)),
395417
rate=2.1 / mockPythonName.flow_rate.aspirate,
396418
)
397419
protocol.delay(seconds=10)
398420
mockPythonName.dispense(
399421
volume=8,
400-
location=mockPythonName["B1"].bottom(z=3.2),
422+
location=mockPythonName["B1"].bottom(z=3.2).move(types.Point(y=1)),
401423
rate=2.2 / mockPythonName.flow_rate.dispense,
402424
)
403425
protocol.delay(seconds=12)
404426
mockPythonName.aspirate(
405427
volume=8,
406-
location=mockPythonName["B1"].bottom(z=3.2),
428+
location=mockPythonName["B1"].bottom(z=3.2).move(types.Point(y=1)),
407429
rate=2.1 / mockPythonName.flow_rate.aspirate,
408430
)
409431
protocol.delay(seconds=10)
410432
mockPythonName.dispense(
411433
volume=8,
412-
location=mockPythonName["B1"].bottom(z=3.2),
434+
location=mockPythonName["B1"].bottom(z=3.2).move(types.Point(y=1)),
413435
rate=2.2 / mockPythonName.flow_rate.dispense,
414436
)
415437
protocol.delay(seconds=12)
@@ -423,25 +445,25 @@ mockPythonName.drop_tip()
423445
mockPythonName.pick_up_tip(location=mockPythonName)
424446
mockPythonName.aspirate(
425447
volume=8,
426-
location=mockPythonName["C1"].bottom(z=3.2),
448+
location=mockPythonName["C1"].bottom(z=3.2).move(types.Point(y=1)),
427449
rate=2.1 / mockPythonName.flow_rate.aspirate,
428450
)
429451
protocol.delay(seconds=10)
430452
mockPythonName.dispense(
431453
volume=8,
432-
location=mockPythonName["C1"].bottom(z=3.2),
454+
location=mockPythonName["C1"].bottom(z=3.2).move(types.Point(y=1)),
433455
rate=2.2 / mockPythonName.flow_rate.dispense,
434456
)
435457
protocol.delay(seconds=12)
436458
mockPythonName.aspirate(
437459
volume=8,
438-
location=mockPythonName["C1"].bottom(z=3.2),
460+
location=mockPythonName["C1"].bottom(z=3.2).move(types.Point(y=1)),
439461
rate=2.1 / mockPythonName.flow_rate.aspirate,
440462
)
441463
protocol.delay(seconds=10)
442464
mockPythonName.dispense(
443465
volume=8,
444-
location=mockPythonName["C1"].bottom(z=3.2),
466+
location=mockPythonName["C1"].bottom(z=3.2).move(types.Point(y=1)),
445467
rate=2.2 / mockPythonName.flow_rate.dispense,
446468
)
447469
protocol.delay(seconds=12)
@@ -454,7 +476,7 @@ mockPythonName.touch_tip(
454476
)
455477
})
456478
})
457-
it('should create python commands with mix() with touchTip and blowOut and no delay set', () => {
479+
it('should create python commands with mix() with touchTip and blowOut and no delay or x/y offset set', () => {
458480
const args: MixArgs = {
459481
...mixinArgs,
460482
touchTip: true,
@@ -463,6 +485,8 @@ mockPythonName.touch_tip(
463485
times,
464486
changeTip: 'always',
465487
wells: ['A1', 'B1', 'C1'],
488+
xOffset: 1,
489+
yOffset: 1,
466490
} as MixArgs
467491

468492
const result = mix(args, invariantContext, robotStateWithTip)
@@ -472,7 +496,12 @@ mockPythonName.touch_tip(
472496
`
473497
mockPythonName.drop_tip()
474498
mockPythonName.pick_up_tip(location=mockPythonName)
475-
mockPythonName.mix(2, 8, mockPythonName["A1"])
499+
mockPythonName.mix(
500+
repetitions=2,
501+
volume=8,
502+
location=mockPythonName["A1"].bottom(z=3.2).move(types.Point(x=1, y=1)),
503+
rate=(2.1 / mockPythonName.flow_rate.aspirate) + (2.2 / mockPythonName.flow_rate.dispense),
504+
)
476505
mockPythonName.flow_rate.blow_out = 2.3
477506
mockPythonName.blow_out(mockPythonName["A1"].top(z=3.3))
478507
mockPythonName.touch_tip(
@@ -481,7 +510,12 @@ mockPythonName.touch_tip(
481510
)
482511
mockPythonName.drop_tip()
483512
mockPythonName.pick_up_tip(location=mockPythonName)
484-
mockPythonName.mix(2, 8, mockPythonName["B1"])
513+
mockPythonName.mix(
514+
repetitions=2,
515+
volume=8,
516+
location=mockPythonName["B1"].bottom(z=3.2).move(types.Point(x=1, y=1)),
517+
rate=(2.1 / mockPythonName.flow_rate.aspirate) + (2.2 / mockPythonName.flow_rate.dispense),
518+
)
485519
mockPythonName.flow_rate.blow_out = 2.3
486520
mockPythonName.blow_out(mockPythonName["A1"].top(z=3.3))
487521
mockPythonName.touch_tip(
@@ -490,7 +524,12 @@ mockPythonName.touch_tip(
490524
)
491525
mockPythonName.drop_tip()
492526
mockPythonName.pick_up_tip(location=mockPythonName)
493-
mockPythonName.mix(2, 8, mockPythonName["C1"])
527+
mockPythonName.mix(
528+
repetitions=2,
529+
volume=8,
530+
location=mockPythonName["C1"].bottom(z=3.2).move(types.Point(x=1, y=1)),
531+
rate=(2.1 / mockPythonName.flow_rate.aspirate) + (2.2 / mockPythonName.flow_rate.dispense),
532+
)
494533
mockPythonName.flow_rate.blow_out = 2.3
495534
mockPythonName.blow_out(mockPythonName["A1"].top(z=3.3))
496535
mockPythonName.touch_tip(

step-generation/src/commandCreators/compound/mix.ts

+29-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
getHasWasteChute,
1414
curryWithoutPython,
1515
formatPyStr,
16+
formatPyWellLocation,
17+
indentPyLines,
1618
} from '../../utils'
1719
import * as errorCreators from '../../errorCreators'
1820
import {
@@ -24,7 +26,10 @@ import {
2426
} from '../atomic'
2527
import { replaceTip } from './replaceTip'
2628

27-
import type { NozzleConfigurationStyle } from '@opentrons/shared-data'
29+
import type {
30+
NozzleConfigurationStyle,
31+
WellLocation,
32+
} from '@opentrons/shared-data'
2833
import type {
2934
MixArgs,
3035
CommandCreator,
@@ -66,10 +71,13 @@ export function mixUtil(args: {
6671
nozzles,
6772
invariantContext,
6873
} = args
69-
// If either delay is specified, emit individual py commands
74+
// If delay is specified, emit individual py commands
7075
// otherwise, emit mix()
71-
const hasDelay = aspirateDelaySeconds != null || dispenseDelaySeconds != null
72-
const curryCreator = hasDelay ? curryCommandCreator : curryWithoutPython
76+
const hasUnsupportedMixApiArg =
77+
aspirateDelaySeconds != null || dispenseDelaySeconds != null
78+
const curryCreator = hasUnsupportedMixApiArg
79+
? curryCommandCreator
80+
: curryWithoutPython
7381

7482
const getDelayCommand = (seconds?: number | null): CurriedCommandCreator[] =>
7583
seconds
@@ -84,13 +92,26 @@ export function mixUtil(args: {
8492
const { pipetteEntities, labwareEntities } = invariantContext
8593
const pipettePythonName = pipetteEntities[pipette].pythonName
8694
const labwarePythonName = labwareEntities[labware].pythonName
95+
const pythonWellLocation: WellLocation = {
96+
origin: 'bottom',
97+
offset: { x: xOffset, y: yOffset, z: offsetFromBottomMm },
98+
}
99+
const pythonArgs = [
100+
`repetitions=${times}`,
101+
`volume=${volume}`,
102+
`location=${labwarePythonName}[${formatPyStr(
103+
well
104+
)}]${formatPyWellLocation(pythonWellLocation)}`,
105+
// Note: just wiring up the aspirate flow rate?
106+
`rate=(${aspirateFlowRateUlSec} / ${pipettePythonName}.flow_rate.aspirate) + (${dispenseFlowRateUlSec} / ${pipettePythonName}.flow_rate.dispense)`,
107+
]
87108
return {
88109
commands: [],
89110
// Note: we do not support mix in trashBin or wasteChute so location
90111
// will always be a well
91-
python: `${pipettePythonName}.mix(${times}, ${volume}, ${labwarePythonName}[${formatPyStr(
92-
well
93-
)}])`,
112+
python: `${pipettePythonName}.mix(\n${indentPyLines(
113+
pythonArgs.join(',\n')
114+
)},\n)`,
94115
}
95116
}
96117
return [
@@ -135,7 +156,7 @@ export function mixUtil(args: {
135156
],
136157
times
137158
),
138-
...(hasDelay ? [] : [pythonCommandCreator]),
159+
...(hasUnsupportedMixApiArg ? [] : [pythonCommandCreator]),
139160
]
140161
}
141162
export const mix: CommandCreator<MixArgs> = (

0 commit comments

Comments
 (0)