Skip to content

Commit 85835ab

Browse files
authored
fix(app): fix the function to retrieve Liquid Class values (#19465)
* fix(app): fix the function to retrieve Liquid Class values
1 parent 6a808e8 commit 85835ab

File tree

8 files changed

+329
-106
lines changed

8 files changed

+329
-106
lines changed

app/src/local-resources/instruments/hooks/usePipetteSpecsv2.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getPipetteSpecsV2 } from '@opentrons/shared-data'
1+
import { getPipetteModelSpecs, getPipetteSpecsV2 } from '@opentrons/shared-data'
22

33
import { useIsOEMMode } from '/app/resources/robot-settings'
44

@@ -12,7 +12,18 @@ export function usePipetteSpecsV2(
1212
name?: PipetteName | PipetteModel
1313
): PipetteV2Specs | null {
1414
const isOEMMode = useIsOEMMode()
15-
const pipetteSpecs = getPipetteSpecsV2(name)
15+
16+
// If it's a PipetteModel (contains 'v'), convert to PipetteName first
17+
let pipetteName: PipetteName | undefined
18+
if (name && name.includes('v')) {
19+
// This is a PipetteModel, get the PipetteName from it
20+
const modelSpecs = getPipetteModelSpecs(name as PipetteModel)
21+
pipetteName = modelSpecs?.name as PipetteName
22+
} else {
23+
pipetteName = name as PipetteName
24+
}
25+
26+
const pipetteSpecs = getPipetteSpecsV2(pipetteName)
1627

1728
if (pipetteSpecs == null) {
1829
return null

app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
StyledText,
1616
} from '@opentrons/components'
1717
import {
18+
FLEX_ROBOT_TYPE,
1819
FLEX_SINGLE_SLOT_BY_CUTOUT_ID,
1920
getAllLiquidClassDefs,
2021
getFlexNameConversion,
@@ -284,6 +285,7 @@ export function BlowOut(props: BlowOutProps): JSX.Element {
284285
flowRateType: 'blowout',
285286
correctionVolume: correctionVolume ?? 0,
286287
shaftULperMM: state.pipette.shaftULperMM,
288+
robotType: FLEX_ROBOT_TYPE,
287289
})
288290

289291
const speedError =

app/src/organisms/ODD/QuickTransferFlow/utils/generateQuickTransferArgs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function getOrderedWells(
5757
return intersection(allWellsOrdered, unorderedWells)
5858
}
5959

60-
function getInvariantContextAndRobotState(
60+
export function getInvariantContextAndRobotState(
6161
quickTransferState: QuickTransferSummaryState
6262
): { invariantContext: InvariantContext; robotState: RobotState } {
6363
const tipRackDefURI = getLabwareDefURI(quickTransferState.tipRack)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import min from 'lodash/min'
2+
3+
import { linearInterpolate } from '@opentrons/shared-data'
4+
5+
import type { LiquidHandlingPropertyByVolume } from '@opentrons/shared-data'
6+
7+
export const getFlowRateFields = (
8+
volume: number,
9+
flowRateByVolume: LiquidHandlingPropertyByVolume,
10+
liquidHandlingAction: 'aspirate' | 'dispense' | 'all',
11+
hardwareMaximum: number | null = null
12+
): Record<string, number | null> => {
13+
const interpolatedFlowRate = linearInterpolate(
14+
volume,
15+
flowRateByVolume as Array<[number, number]>
16+
)
17+
return {
18+
[`${liquidHandlingAction}_flowRate`]:
19+
hardwareMaximum != null
20+
? min([interpolatedFlowRate, hardwareMaximum]) ?? null
21+
: interpolatedFlowRate,
22+
}
23+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { getAllLabwareDefs, LOW_VOLUME_PIPETTES } from '@opentrons/shared-data'
2+
3+
import { getPipetteNameFromSpecs } from './getPipetteNameFromSpecs'
4+
5+
import type { PipetteV2Specs, SupportedTip } from '@opentrons/shared-data'
6+
7+
const LOW_PIPETTE_VOLUME = 5
8+
9+
export function getMatchingTipLiquidSpecsFromSpec(
10+
pipetteSpecs: PipetteV2Specs,
11+
volume: number,
12+
tiprackUri: string
13+
): SupportedTip {
14+
const matchingLabwareDef = getAllLabwareDefs()[tiprackUri]
15+
const pipetteName = getPipetteNameFromSpecs(pipetteSpecs)
16+
17+
console.assert(
18+
matchingLabwareDef,
19+
`expected to find a matching labware def with tiprack ${tiprackUri} but could not`
20+
)
21+
22+
const tipLength = matchingLabwareDef?.parameters.tipLength ?? 0
23+
24+
if (tipLength === 0) {
25+
console.error(
26+
`expected to find a tiplength with tiprack ${
27+
matchingLabwareDef?.metadata.displayName ?? 'unknown displayName'
28+
} but could not`
29+
)
30+
}
31+
32+
const isLowVolumePipette = LOW_VOLUME_PIPETTES.includes(pipetteName)
33+
34+
const isUsingLowVolume = volume < LOW_PIPETTE_VOLUME
35+
const liquidType =
36+
isLowVolumePipette && isUsingLowVolume ? 'lowVolumeDefault' : 'default'
37+
38+
const liquidSupportedTips = Object.values(
39+
pipetteSpecs.liquids[liquidType].supportedTips
40+
)
41+
42+
// find the supported tip liquid specs that either exactly match
43+
// tipLength or are closest, this accounts for custom tipracks
44+
const matchingTipLiquidSpecs = liquidSupportedTips.sort((tipA, tipB) => {
45+
const differenceA = Math.abs(tipA.defaultTipLength - tipLength)
46+
const differenceB = Math.abs(tipB.defaultTipLength - tipLength)
47+
return differenceA - differenceB
48+
})[0]
49+
50+
console.assert(
51+
matchingTipLiquidSpecs,
52+
`expected to find the tip liquid specs but could not with pipette tiprack displayname ${
53+
matchingLabwareDef?.metadata.displayName ?? 'unknown displayname'
54+
}`
55+
)
56+
57+
return matchingTipLiquidSpecs
58+
}
Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,63 @@
11
import round from 'lodash/round'
22

3-
import type { PipetteChannels, SupportedTip } from '@opentrons/shared-data'
3+
import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data'
4+
5+
import type {
6+
PipetteChannels,
7+
RobotType,
8+
SupportedTip,
9+
} from '@opentrons/shared-data'
410

511
type FlowRateType = 'aspirate' | 'dispense' | 'blowout'
612

7-
const FLEX_X_Y_MAX_SPEED = 300
8-
const FLEX_LOW_THROUGHPUT_Z_MAX_SPEED = 100
9-
const FLEX_HIGH_THROUGHPUT_Z_MAX_SPEED = 35
10-
const FLEX_LOW_THROUGHPUT_PLUNGER_MAX_SPEED = 70
11-
const FLEX_HIGH_THROUGHPUT_PLUNGER_MAX_SPEED = 15
13+
// Note(kk:2025-09-05): keep OT-2 values since we will move the same functions from (app&pd) to shared-data after the release
14+
export const FLEX_X_Y_MAX_SPEED = 300
15+
export const FLEX_LOW_THROUGHPUT_Z_MAX_SPEED = 100
16+
export const FLEX_HIGH_THROUGHPUT_Z_MAX_SPEED = 35
17+
export const FLEX_LOW_THROUGHPUT_PLUNGER_MAX_SPEED = 70
18+
export const FLEX_HIGH_THROUGHPUT_PLUNGER_MAX_SPEED = 15
19+
export const OT2_X_MAX_SPEED = 600
20+
export const OT2_Y_MAX_SPEED = 400
21+
export const OT2_Z_MAX_SPEED = 125
22+
export const OT2_PLUNGER_MAX_SPEED = 40
1223

1324
const CHANNELS_MAPPED_TO_MAX_SPEED: Record<
14-
number,
15-
{ plunger: number; x: number; y: number; z: number }
25+
RobotType,
26+
Record<number, { plunger: number; x: number; y: number; z: number }>
1627
> = {
17-
1: {
18-
plunger: FLEX_LOW_THROUGHPUT_PLUNGER_MAX_SPEED,
19-
x: FLEX_X_Y_MAX_SPEED,
20-
y: FLEX_X_Y_MAX_SPEED,
21-
z: FLEX_LOW_THROUGHPUT_Z_MAX_SPEED,
22-
},
23-
8: {
24-
plunger: FLEX_LOW_THROUGHPUT_PLUNGER_MAX_SPEED,
25-
x: FLEX_X_Y_MAX_SPEED,
26-
y: FLEX_X_Y_MAX_SPEED,
27-
z: FLEX_LOW_THROUGHPUT_Z_MAX_SPEED,
28+
[FLEX_ROBOT_TYPE]: {
29+
1: {
30+
plunger: FLEX_LOW_THROUGHPUT_PLUNGER_MAX_SPEED,
31+
x: FLEX_X_Y_MAX_SPEED,
32+
y: FLEX_X_Y_MAX_SPEED,
33+
z: FLEX_LOW_THROUGHPUT_Z_MAX_SPEED,
34+
},
35+
8: {
36+
plunger: FLEX_LOW_THROUGHPUT_PLUNGER_MAX_SPEED,
37+
x: FLEX_X_Y_MAX_SPEED,
38+
y: FLEX_X_Y_MAX_SPEED,
39+
z: FLEX_LOW_THROUGHPUT_Z_MAX_SPEED,
40+
},
41+
96: {
42+
plunger: FLEX_HIGH_THROUGHPUT_PLUNGER_MAX_SPEED,
43+
x: FLEX_X_Y_MAX_SPEED,
44+
y: FLEX_X_Y_MAX_SPEED,
45+
z: FLEX_HIGH_THROUGHPUT_Z_MAX_SPEED,
46+
},
2847
},
29-
96: {
30-
plunger: FLEX_HIGH_THROUGHPUT_PLUNGER_MAX_SPEED,
31-
x: FLEX_X_Y_MAX_SPEED,
32-
y: FLEX_X_Y_MAX_SPEED,
33-
z: FLEX_HIGH_THROUGHPUT_Z_MAX_SPEED,
48+
[OT2_ROBOT_TYPE]: {
49+
1: {
50+
plunger: OT2_PLUNGER_MAX_SPEED,
51+
x: OT2_X_MAX_SPEED,
52+
y: OT2_Y_MAX_SPEED,
53+
z: OT2_Z_MAX_SPEED,
54+
},
55+
8: {
56+
plunger: OT2_PLUNGER_MAX_SPEED,
57+
x: OT2_X_MAX_SPEED,
58+
y: OT2_Y_MAX_SPEED,
59+
z: OT2_Z_MAX_SPEED,
60+
},
3461
},
3562
}
3663

@@ -42,6 +69,7 @@ const _getPipetteAccuracyUlPerMm = (args: {
4269
const { targetVolume, tipLiquidSpecs, flowRateType } = args
4370

4471
const flowRateFunction = tipLiquidSpecs[flowRateType].default['1']
72+
4573
let pipetteAccuracyUlPerMm = null
4674
for (let i = 0; i < flowRateFunction.length; i++) {
4775
const [x, y, z] = flowRateFunction[i]
@@ -51,7 +79,8 @@ const _getPipetteAccuracyUlPerMm = (args: {
5179
}
5280
}
5381
const lastEntry = flowRateFunction[flowRateFunction.length - 1]
54-
return lastEntry[1] * targetVolume + lastEntry[2]
82+
const result = lastEntry[1] * targetVolume + lastEntry[2]
83+
return result
5584
}
5685

5786
export const getMaxUiFlowRate = (args: {
@@ -61,6 +90,7 @@ export const getMaxUiFlowRate = (args: {
6190
flowRateType: FlowRateType
6291
correctionVolume: number
6392
shaftULperMM: number
93+
robotType: RobotType
6494
}): number => {
6595
const {
6696
targetVolume,
@@ -69,19 +99,27 @@ export const getMaxUiFlowRate = (args: {
6999
flowRateType,
70100
correctionVolume,
71101
shaftULperMM,
102+
robotType,
72103
} = args
73104

74-
const maxPlungerSpeed = CHANNELS_MAPPED_TO_MAX_SPEED[channels].plunger
105+
const maxPlungerSpeed =
106+
CHANNELS_MAPPED_TO_MAX_SPEED[robotType][channels].plunger
107+
75108
if (flowRateType === 'blowout') {
76-
return round(shaftULperMM * maxPlungerSpeed)
109+
const result = round(shaftULperMM * maxPlungerSpeed)
110+
return result
77111
}
112+
78113
const pipetteAccuracyUlPerMm = _getPipetteAccuracyUlPerMm({
79114
targetVolume,
80115
tipLiquidSpecs,
81116
flowRateType,
82117
})
118+
83119
const correctionMultiplier = 1.0 + correctionVolume / targetVolume
84120
const travelMm = targetVolume / pipetteAccuracyUlPerMm
85121
const travelMmCorrected = travelMm * correctionMultiplier
86-
return round(targetVolume / (travelMmCorrected / maxPlungerSpeed))
122+
const result = round(targetVolume / (travelMmCorrected / maxPlungerSpeed))
123+
124+
return result
87125
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { PipetteV2Specs } from '@opentrons/shared-data'
2+
3+
export const getPipetteNameFromSpecs = (
4+
pipetteSpecs: PipetteV2Specs
5+
): string => {
6+
const { model, channels, displayCategory } = pipetteSpecs
7+
8+
const channelSuffix =
9+
channels === 1
10+
? 'single'
11+
: channels === 8
12+
? 'multi'
13+
: channels === 96
14+
? '96'
15+
: 'single'
16+
17+
// Note(kk:2025-09-05): This is used for Flex only
18+
const robotSuffix = displayCategory === 'FLEX' && 'flex'
19+
20+
return `${model}_${channelSuffix}_${robotSuffix}`
21+
}

0 commit comments

Comments
 (0)