Skip to content

Commit 8b0dc14

Browse files
authored
fix(protocol-designer,step-generation): fix reference volumes in getLiquidClassValuesMoveLiquid (#18524)
This PR ensures that we utilize the transfer plan reference volumes when populating the moveLiquid advanced settings page. When populating the advanced settings in `getLiquidClassValueMoveLiquid`, we must use the effective tip volume, whether split for a single path, or inclusive of many source/destination volumes for a multiDispense/Aspirate.
1 parent e94c0e9 commit 8b0dc14

File tree

7 files changed

+125
-50
lines changed

7 files changed

+125
-50
lines changed

protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/FlowRateField.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,26 @@ export function FlowRateField(props: FlowRateFieldProps): JSX.Element {
7171
({ labwareDefURI }) => labwareDefURI === tiprack
7272
)?.def ?? null
7373

74+
let airGapByVolume: Array<[number, number]> = []
75+
// no air gap included for mix step
76+
if (formData?.stepType === 'moveLiquid') {
77+
if (flowRateType === 'aspirate') {
78+
airGapByVolume =
79+
(liquidClassValuesForTip?.aspirate.retract.airGapByVolume as Array<
80+
[number, number]
81+
>) ?? []
82+
} else if (flowRateType === 'dispense') {
83+
airGapByVolume =
84+
formData?.stepType === 'moveLiquid' &&
85+
formData.path === 'multiDispense' &&
86+
liquidClassValuesForTip != null &&
87+
'multiDispense' in liquidClassValuesForTip
88+
? (liquidClassValuesForTip.multiDispense?.retract
89+
.airGapByVolume as Array<[number, number]>) ?? []
90+
: (liquidClassValuesForTip?.singleDispense.retract
91+
.airGapByVolume as Array<[number, number]>) ?? []
92+
}
93+
}
7494
// if form type is 'mix', we will use single path
7595
const referenceVolumesForByVolumeInterpolation =
7696
pipette != null && tiprackDef != null && formData != null
@@ -90,10 +110,7 @@ export function FlowRateField(props: FlowRateFieldProps): JSX.Element {
90110
(liquidClassValuesForTip?.multiDispense?.disposalByVolume as Array<
91111
[number, number]
92112
>) ?? null,
93-
aspirateAirGap:
94-
formData.aspirate_airGap_checkbox === true
95-
? formData.aspirate_airGap_volume
96-
: null,
113+
aspirateAirGapByVolume: airGapByVolume,
97114
}).referenceVolumes
98115
: null
99116
const [referenceVolumeFlowRate, referenceVolumeCorrection] =

protocol-designer/src/steplist/formLevel/handleFormChange/utils.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import type {
3535
AdditionalEquipmentEntities,
3636
LabwareEntities,
3737
PipetteEntities,
38+
ReferenceVolumes,
3839
} from '@opentrons/step-generation'
3940
import type { FormData, PathOption, StepFieldName } from '../../../form-types'
4041
import type {
@@ -403,7 +404,7 @@ const getByVolumeField = (args: {
403404

404405
const getSubmergeRetractFields = (args: {
405406
submergeRetractLookup: Submerge | RetractAspirate | RetractDispense
406-
volume: number
407+
volumes: ReferenceVolumes
407408
liquidHandlingAction: LiquidHandlingTab
408409
tipMovement: 'submerge' | 'retract'
409410
additionalEquipmentEntities?: AdditionalEquipmentEntities
@@ -412,7 +413,7 @@ const getSubmergeRetractFields = (args: {
412413
}): Record<string, any> => {
413414
const {
414415
submergeRetractLookup,
415-
volume,
416+
volumes,
416417
liquidHandlingAction,
417418
tipMovement,
418419
additionalEquipmentEntities,
@@ -439,7 +440,7 @@ const getSubmergeRetractFields = (args: {
439440
const airGapFields =
440441
'airGapByVolume' in submergeRetractLookup && !isConditioningVolumeEnabled
441442
? getByVolumeField({
442-
volume,
443+
volume: volumes.airGap,
443444
byVolume: submergeRetractLookup.airGapByVolume,
444445
field: 'airGap',
445446
prefix: liquidHandlingAction,
@@ -728,13 +729,12 @@ const getLiquidClassValuesMoveLiquid = (args: {
728729
tiprackDefinition,
729730
conditioningByVolume,
730731
disposalByVolume,
731-
volume: Number(rawForm.volume),
732+
volume,
732733
path: rawForm.path as PathOption,
733734
numDispenseWells: rawForm.dispense_wells.length,
734-
aspirateAirGap:
735-
rawForm.aspirate_airGap_checkbox === true
736-
? Number(rawForm.aspirate_airGap_volume)
737-
: null,
735+
aspirateAirGapByVolume: aspirate.retract.airGapByVolume as Array<
736+
[number, number]
737+
>,
738738
}).referenceVolumes
739739
// top-level aspirate fields
740740
const aspiratePositionReferenceFields = getPositionReferenceFields(
@@ -795,7 +795,7 @@ const getLiquidClassValuesMoveLiquid = (args: {
795795
// aspirate/dispense submerge fields
796796
const aspirateSubmergeFields = getSubmergeRetractFields({
797797
submergeRetractLookup: aspirate.submerge,
798-
volume: Number(volume),
798+
volumes: byVolumeLookup,
799799
liquidHandlingAction: 'aspirate',
800800
tipMovement: 'submerge',
801801
additionalEquipmentEntities,
@@ -805,15 +805,15 @@ const getLiquidClassValuesMoveLiquid = (args: {
805805
path === 'multiDispense' && multiDispense != null
806806
? multiDispense.submerge
807807
: singleDispense.submerge,
808-
volume: Number(volume),
808+
volumes: byVolumeLookup,
809809
liquidHandlingAction: 'dispense',
810810
tipMovement: 'submerge',
811811
})
812812

813813
// aspirate/dispense retract fields
814814
const aspirateRetractFields = getSubmergeRetractFields({
815815
submergeRetractLookup: aspirate.retract,
816-
volume: Number(volume),
816+
volumes: byVolumeLookup,
817817
liquidHandlingAction: 'aspirate',
818818
tipMovement: 'retract',
819819
isConditioningVolumeEnabled,
@@ -823,7 +823,7 @@ const getLiquidClassValuesMoveLiquid = (args: {
823823
path === 'multiDispense' && multiDispense != null
824824
? multiDispense.retract
825825
: singleDispense.retract,
826-
volume: Number(volume),
826+
volumes: byVolumeLookup,
827827
liquidHandlingAction: 'dispense',
828828
tipMovement: 'retract',
829829
additionalEquipmentEntities,

protocol-designer/src/steplist/formLevel/stepFormToArgs/moveLiquidFormToArgs.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ const getCheckedPath = (
119119
volume,
120120
path,
121121
numDispenseWells: hydratedFormData.dispense_wells.length,
122+
aspirateAirGapByVolume: liquidClassValuesForTip.aspirate.retract
123+
.airGapByVolume as Array<[number, number]>,
122124
conditioningByVolume: null,
123125
disposalByVolume: null,
124126
})
@@ -128,6 +130,8 @@ const getCheckedPath = (
128130
volume,
129131
path,
130132
numDispenseWells: hydratedFormData.dispense_wells.length,
133+
aspirateAirGapByVolume: liquidClassValuesForTip.aspirate.retract
134+
.airGapByVolume as Array<[number, number]>,
131135
conditioningByVolume:
132136
(liquidClassValuesForTip.multiDispense
133137
?.conditioningByVolume as Array<[number, number]>) ?? null,

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,25 +200,26 @@ export const consolidate: CommandCreator<ConsolidateArgs> = (
200200
spec: pipetteSpecs,
201201
name: pipetteName,
202202
} = invariantContext.pipetteEntities[pipette]
203-
const multiDispenseValuesForTip = getAllLiquidClassDefs()
203+
const liquidClassValuesForTip = getAllLiquidClassDefs()
204204
[
205205
liquidClass === NONE_LIQUID_CLASS_NAME || liquidClass == null
206206
? WATER_LIQUID_CLASS_NAME
207207
: liquidClass
208208
].byPipette?.find(
209209
({ pipetteModel }) => (pipetteModel = getFlexNameConversion(pipetteSpecs))
210210
)
211-
?.byTipType.find(({ tiprack }) => tiprack === tiprackDefUri)?.multiDispense
211+
?.byTipType.find(({ tiprack }) => tiprack === tiprackDefUri)
212+
const { aspirate } = liquidClassValuesForTip ?? {}
212213
const { multiWellHandling } = getTransferPlanAndReferenceVolumes({
213214
pipetteSpecs,
214215
tiprackDefinition,
215216
volume,
216217
path: 'multiAspirate',
217218
numDispenseWells: sourceWells.length,
218-
conditioningByVolume: (multiDispenseValuesForTip?.conditioningByVolume ??
219-
[]) as Array<[number, number]>,
220-
disposalByVolume: (multiDispenseValuesForTip?.disposalByVolume ??
221-
[]) as Array<[number, number]>,
219+
aspirateAirGapByVolume:
220+
(aspirate?.retract.airGapByVolume as Array<[number, number]>) ?? [],
221+
conditioningByVolume: null,
222+
disposalByVolume: null,
222223
})
223224

224225
const { numWellsToFitInTip } = multiWellHandling

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,26 +211,30 @@ export const distribute: CommandCreator<DistributeArgs> = (
211211
spec: pipetteSpecs,
212212
name: pipetteName,
213213
} = invariantContext.pipetteEntities[pipette]
214-
const multiDispenseValuesForTip = getAllLiquidClassDefs()
214+
const liquidClassValuesForTip = getAllLiquidClassDefs()
215215
[
216216
liquidClass === NONE_LIQUID_CLASS_NAME || liquidClass == null
217217
? WATER_LIQUID_CLASS_NAME
218218
: liquidClass
219219
].byPipette?.find(
220220
({ pipetteModel }) => (pipetteModel = getFlexNameConversion(pipetteSpecs))
221221
)
222-
?.byTipType.find(({ tiprack }) => tiprack === tiprackDefUri)?.multiDispense
222+
?.byTipType.find(({ tiprack }) => tiprack === tiprackDefUri)
223+
const { aspirate, multiDispense } = liquidClassValuesForTip ?? {}
223224
const { multiWellHandling } = getTransferPlanAndReferenceVolumes({
224225
pipetteSpecs,
225226
tiprackDefinition,
226227
volume,
227228
path: 'multiDispense',
228229
numDispenseWells: destWells.length,
229-
conditioningByVolume: (multiDispenseValuesForTip?.conditioningByVolume ??
230-
[]) as Array<[number, number]>,
231-
disposalByVolume: (multiDispenseValuesForTip?.disposalByVolume ??
232-
[]) as Array<[number, number]>,
233-
aspirateAirGap: aspirateAirGapVolume,
230+
conditioningByVolume: (multiDispense?.conditioningByVolume ?? []) as Array<
231+
[number, number]
232+
>,
233+
disposalByVolume: (multiDispense?.disposalByVolume ?? []) as Array<
234+
[number, number]
235+
>,
236+
aspirateAirGapByVolume:
237+
(aspirate?.retract.airGapByVolume as Array<[number, number]>) ?? [],
234238
})
235239
const { numWellsToFitInTip } = multiWellHandling
236240

step-generation/src/utils/__tests__/misc.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
5555
volume: 5,
5656
path: 'single',
5757
numDispenseWells: 1,
58+
aspirateAirGapByVolume: [],
5859
conditioningByVolume: null,
5960
disposalByVolume: null,
6061
})
@@ -80,6 +81,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
8081
volume: 15,
8182
path: 'single',
8283
numDispenseWells: 1,
84+
aspirateAirGapByVolume: [],
8385
conditioningByVolume: null,
8486
disposalByVolume: null,
8587
})
@@ -99,6 +101,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
99101
volume: 15,
100102
path: 'single',
101103
numDispenseWells: 1,
104+
aspirateAirGapByVolume: [],
102105
conditioningByVolume: null,
103106
disposalByVolume: null,
104107
})
@@ -111,13 +114,34 @@ describe('getTransferPlanAndReferenceVolumes', () => {
111114
expect(result.multiWellHandling.isSupported).toBe(false)
112115
})
113116

117+
it('should handle volumes exceeding tiprack max volume for single path with air gap', () => {
118+
const result = getTransferPlanAndReferenceVolumes({
119+
pipetteSpecs: MOCK_P300_SPECS,
120+
tiprackDefinition: fixtureTiprack10ul,
121+
volume: 10,
122+
path: 'single',
123+
numDispenseWells: 1,
124+
aspirateAirGapByVolume: [[10, 2]],
125+
conditioningByVolume: null,
126+
disposalByVolume: null,
127+
})
128+
expect(result.referenceVolumes.airGap).toBeCloseTo(5)
129+
expect(result.referenceVolumes.correctionAspirate).toBeCloseTo(5)
130+
expect(result.referenceVolumes.correctionDispense).toBeCloseTo(5)
131+
expect(result.referenceVolumes.pushOut).toBeCloseTo(5)
132+
expect(result.referenceVolumes.flowRateAspirate).toBeCloseTo(5)
133+
expect(result.referenceVolumes.flowRateDispense).toBeCloseTo(5)
134+
expect(result.multiWellHandling.isSupported).toBe(false)
135+
})
136+
114137
it('should return correct values for multiAspirate path', () => {
115138
const result = getTransferPlanAndReferenceVolumes({
116139
pipetteSpecs: MOCK_P10_SPECS,
117140
tiprackDefinition: fixtureTiprack10ul,
118141
volume: 3,
119142
path: 'multiAspirate',
120143
numDispenseWells: 1,
144+
aspirateAirGapByVolume: [],
121145
conditioningByVolume: null,
122146
disposalByVolume: null,
123147
})
@@ -144,6 +168,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
144168
volume: 4,
145169
path: 'multiAspirate',
146170
numDispenseWells: 1,
171+
aspirateAirGapByVolume: [],
147172
conditioningByVolume: null,
148173
disposalByVolume: null,
149174
})
@@ -158,6 +183,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
158183
volume: 4,
159184
path: 'multiAspirate',
160185
numDispenseWells: 1,
186+
aspirateAirGapByVolume: [],
161187
conditioningByVolume: null,
162188
disposalByVolume: null,
163189
})
@@ -172,6 +198,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
172198
volume: 10,
173199
path: 'multiDispense',
174200
numDispenseWells: 3,
201+
aspirateAirGapByVolume: [],
175202
conditioningByVolume: [
176203
[20, 5],
177204
[40, 10],
@@ -200,6 +227,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
200227
volume: 3,
201228
path: 'multiDispense',
202229
numDispenseWells: 4,
230+
aspirateAirGapByVolume: [],
203231
conditioningByVolume: [
204232
[5, 0],
205233
[7, 2],
@@ -228,6 +256,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
228256
volume: 3,
229257
path: 'multiDispense',
230258
numDispenseWells: 4,
259+
aspirateAirGapByVolume: [],
231260
conditioningByVolume: [
232261
[5, 0],
233262
[7, 2],
@@ -256,6 +285,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
256285
volume: 6,
257286
path: 'multiDispense',
258287
numDispenseWells: 2,
288+
aspirateAirGapByVolume: [],
259289
conditioningByVolume: [[10, 1]],
260290
disposalByVolume: [[10, 0.5]],
261291
})
@@ -282,6 +312,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
282312
volume: 1,
283313
path: 'single',
284314
numDispenseWells: 1,
315+
aspirateAirGapByVolume: [],
285316
conditioningByVolume: null,
286317
disposalByVolume: null,
287318
})
@@ -304,6 +335,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
304335
volume: 0.8,
305336
path: 'single',
306337
numDispenseWells: 1,
338+
aspirateAirGapByVolume: [],
307339
conditioningByVolume: null,
308340
disposalByVolume: null,
309341
})

0 commit comments

Comments
 (0)