Skip to content

Commit

Permalink
feat(protocol-designer, step-generation): custom z offset for blowout (
Browse files Browse the repository at this point in the history
  • Loading branch information
jerader authored Apr 4, 2024
1 parent 5c3f08b commit 048a533
Show file tree
Hide file tree
Showing 39 changed files with 454 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"dispense_touchTip_mmFromBottom": 40,
"disposalVolume_checkbox": true,
"disposalVolume_volume": "20",
"blowout_z_offset": 0,
"blowout_checkbox": false,
"blowout_location": "8053a205-f2dc-4b1d-8d05-bf8233949e2e:trashBin",
"preWetTip": false,
Expand Down Expand Up @@ -157,6 +158,7 @@
"labware": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1",
"mix_wellOrder_first": "t2b",
"mix_wellOrder_second": "l2r",
"blowout_z_offset": 0,
"blowout_checkbox": true,
"blowout_location": "8053a205-f2dc-4b1d-8d05-bf8233949e2e:trashBin",
"mix_mmFromBottom": 0.5,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"dispense_touchTip_mmFromBottom": null,
"disposalVolume_checkbox": true,
"disposalVolume_volume": "20",
"blowout_z_offset": 0,
"blowout_checkbox": false,
"blowout_location": "84882326-9cd3-428e-8352-89f133a1fe5d:trashBin",
"preWetTip": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@
"dispense_touchTip_mmFromBottom": null,
"disposalVolume_checkbox": true,
"disposalVolume_volume": "100",
"blowout_z_offset": 0,
"blowout_checkbox": false,
"blowout_location": "4824b094-5999-4549-9e6b-7098a9b30a8b:trashBin",
"preWetTip": false,
Expand Down Expand Up @@ -209,6 +210,7 @@
"labware": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2",
"mix_wellOrder_first": "t2b",
"mix_wellOrder_second": "l2r",
"blowout_z_offset": 0,
"blowout_checkbox": false,
"blowout_location": "4824b094-5999-4549-9e6b-7098a9b30a8b:trashBin",
"mix_mmFromBottom": 0.5,
Expand Down
1 change: 1 addition & 0 deletions protocol-designer/fixtures/protocol/8/doItAllV8.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
"dispense_y_position": 0,
"aspirate_x_position": 0,
"aspirate_y_position": 0,
"blowout_z_offset": 0,
"id": "d2f74144-a7bf-4ba2-aaab-30d70b2b62c7",
"stepType": "moveLiquid",
"stepName": "transfer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"disposalVolume_checkbox": true,
"disposalVolume_volume": "1",
"blowout_checkbox": true,
"blowout_z_offset": 0,
"blowout_location": "9b1c0d01-9d4f-4016-afe6-9e08b46acf5e:trashBin",
"preWetTip": false,
"aspirate_airGap_checkbox": false,
Expand Down Expand Up @@ -143,6 +144,7 @@
"labware": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well",
"mix_wellOrder_first": "t2b",
"mix_wellOrder_second": "l2r",
"blowout_z_offset": 0,
"blowout_checkbox": true,
"blowout_location": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well",
"mix_mmFromBottom": 0.5,
Expand Down Expand Up @@ -5695,7 +5697,7 @@
"labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well",
"wellName": "A1",
"flowRate": 7,
"wellLocation": { "origin": "bottom", "offset": { "z": 41.3 } }
"wellLocation": { "origin": "top", "offset": { "z": 0 } }
}
},
{
Expand Down
1 change: 1 addition & 0 deletions protocol-designer/fixtures/protocol/8/mix_8_0_0.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"labware": null,
"mix_wellOrder_first": "t2b",
"mix_wellOrder_second": "l2r",
"blowout_z_offset": 0,
"blowout_checkbox": false,
"blowout_location": "5ba7047d-d3e2-4845-9eaa-1974af796ead:trashBin",
"mix_mmFromBottom": 0.5,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"dispense_touchTip_mmFromBottom": null,
"disposalVolume_checkbox": true,
"disposalVolume_volume": "5",
"blowout_z_offset": 0,
"blowout_checkbox": false,
"blowout_location": null,
"preWetTip": false,
Expand Down Expand Up @@ -133,6 +134,7 @@
"dispense_touchTip_mmFromBottom": null,
"disposalVolume_checkbox": true,
"disposalVolume_volume": "5",
"blowout_z_offset": 0,
"blowout_checkbox": false,
"blowout_location": null,
"preWetTip": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import styles from '../StepEditForm.module.css'
import { FieldProps } from '../types'

type BlowoutLocationDropdownProps = FieldProps & {
className?: string
options: Options
className?: string
}

export const BlowoutLocationField = (
Expand All @@ -28,7 +28,7 @@ export const BlowoutLocationField = (

return (
<DropdownField
className={cx(styles.large_field, className)}
className={cx(styles.small_field, className)}
options={options}
disabled={disabled}
id={'BlowoutLocationField_dropdown'}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as React from 'react'
import { useSelector } from 'react-redux'
import {
DEST_WELL_BLOWOUT_DESTINATION,
SOURCE_WELL_BLOWOUT_DESTINATION,
} from '@opentrons/step-generation'
import {
COLORS,
Flex,
Icon,
Tooltip,
useHoverTooltip,
} from '@opentrons/components'
import { getLabwareEntities } from '../../../step-forms/selectors'
import { ZTipPositionModal } from './TipPositionField/ZTipPositionModal'
import type { FieldProps } from '../types'

interface BlowoutZOffsetFieldProps extends FieldProps {
destLabwareId: unknown
sourceLabwareId?: unknown
blowoutLabwareId?: unknown
}

export function BlowoutZOffsetField(
props: BlowoutZOffsetFieldProps
): JSX.Element {
const {
disabled,
value,
destLabwareId,
sourceLabwareId,
blowoutLabwareId,
tooltipContent,
name,
updateValue,
} = props
const [isModalOpen, setModalOpen] = React.useState<boolean>(false)
const [targetProps, tooltipProps] = useHoverTooltip()
const labwareEntities = useSelector(getLabwareEntities)

let labwareId = null
if (blowoutLabwareId === SOURCE_WELL_BLOWOUT_DESTINATION) {
labwareId = sourceLabwareId
} else if (blowoutLabwareId === DEST_WELL_BLOWOUT_DESTINATION) {
labwareId = destLabwareId
}

const labwareZDimension =
labwareId != null
? labwareEntities[String(labwareId)]?.def.dimensions.zDimension
: 0

return (
<>
<Tooltip {...tooltipProps}>{tooltipContent}</Tooltip>
{isModalOpen ? (
<ZTipPositionModal
closeModal={() => setModalOpen(false)}
name={name}
zValue={Number(value)}
updateValue={updateValue}
wellDepthMm={labwareZDimension}
/>
) : null}
<Flex
{...targetProps}
onClick={disabled ? undefined : () => setModalOpen(true)}
id={`BlowoutZOffsetField_${name}`}
data-testid={`BlowoutZOffsetField_${name}`}
>
<Icon
name="ot-calibrate"
size="1.5rem"
cursor="pointer"
color={disabled ? COLORS.grey30 : COLORS.grey50}
/>
</Flex>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
font-weight: var(--fw-semibold);
color: var(--c-blue);
position: absolute;
right: 10px;
bottom: 45px;
align-self: flex-end;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@ import styles from './TipPositionInput.module.css'

const WELL_HEIGHT_PIXELS = 145
const PIXEL_DECIMALS = 2
interface Props {
mmFromBottom: number
interface TipPositionZAxisVizProps {
wellDepthMm: number
mmFromBottom?: number
mmFromTop?: number
}

export const TipPositionZAxisViz = (props: Props): JSX.Element => {
const fractionOfWellHeight = props.mmFromBottom / props.wellDepthMm
export function TipPositionZAxisViz(
props: TipPositionZAxisVizProps
): JSX.Element {
const { mmFromBottom, mmFromTop, wellDepthMm } = props
const positionInTube = mmFromBottom ?? mmFromTop ?? 0
const fractionOfWellHeight = positionInTube / wellDepthMm
const pixelsFromBottom =
Number(fractionOfWellHeight) * WELL_HEIGHT_PIXELS - WELL_HEIGHT_PIXELS
const roundedPixelsFromBottom = round(pixelsFromBottom, PIXEL_DECIMALS)
const bottomPx = props.wellDepthMm
? roundedPixelsFromBottom
: props.mmFromBottom - WELL_HEIGHT_PIXELS
fractionOfWellHeight * WELL_HEIGHT_PIXELS -
(mmFromBottom != null ? WELL_HEIGHT_PIXELS : 0)
const bottomPx = round(pixelsFromBottom, PIXEL_DECIMALS)

return (
<div className={styles.viz_wrapper}>
<img
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
OutlineButton,
RadioGroup,
} from '@opentrons/components'
import { DEFAULT_MM_BLOWOUT_OFFSET_FROM_TOP } from '../../../../constants'
import { getMainPagePortalEl } from '../../../portals/MainPageModalPortal'
import { getIsTouchTipField } from '../../../../form-types'
import { TipPositionZAxisViz } from './TipPositionZAxisViz'
Expand All @@ -24,7 +25,7 @@ import styles from './TipPositionInput.module.css'

interface ZTipPositionModalProps {
closeModal: () => void
mmFromBottom: number | null
zValue: number | null
name: StepFieldName
updateValue: (val?: number | null) => unknown
wellDepthMm: number
Expand All @@ -36,21 +37,26 @@ export function ZTipPositionModal(props: ZTipPositionModalProps): JSX.Element {
isIndeterminate,
name,
wellDepthMm,
mmFromBottom,
zValue,
closeModal,
updateValue,
} = props
const { t } = useTranslation(['modal', 'button'])
const defaultMmFromBottom = utils.getDefaultMmFromBottom({
name,
wellDepthMm,
})

const isBlowout = name === 'blowout_z_offset'
const defaultMm = isBlowout
? 0
: utils.getDefaultMmFromBottom({
name,
wellDepthMm,
})

const [value, setValue] = React.useState<string | null>(
mmFromBottom === null ? null : String(mmFromBottom)
zValue !== null ? String(zValue) : null
)
const isSetDefault = isBlowout ? zValue === 0 : zValue === null
const [isDefault, setIsDefault] = React.useState<boolean>(
!isIndeterminate && mmFromBottom === null
!isIndeterminate && isSetDefault
)
// in this modal, pristinity hides the OUT_OF_BOUNDS error only.
const [isPristine, setPristine] = React.useState<boolean>(true)
Expand All @@ -71,20 +77,29 @@ export function ZTipPositionModal(props: ZTipPositionModalProps): JSX.Element {
}
}
const { maxMmFromBottom, minMmFromBottom } = getMinMaxMmFromBottom()

// For blowout from the top of the well
const minFromTop = DEFAULT_MM_BLOWOUT_OFFSET_FROM_TOP
const maxFromTop = -wellDepthMm

const minMm = isBlowout ? maxFromTop : minMmFromBottom
const maxMm = isBlowout ? minFromTop : maxMmFromBottom

const errors = utils.getErrors({
isDefault,
minMm: minMmFromBottom,
maxMm: maxMmFromBottom,
minMm,
maxMm,
value,
})
const hasErrors = errors.length > 0
const hasVisibleErrors = isPristine
? errors.includes(TOO_MANY_DECIMALS)
: hasErrors

const errorText = utils.getErrorText({
errors,
minMm: maxMmFromBottom,
maxMm: minMmFromBottom,
minMm,
maxMm,
isPristine,
t,
})
Expand All @@ -110,13 +125,17 @@ export function ZTipPositionModal(props: ZTipPositionModalProps): JSX.Element {
// if string, strip non-number characters from string and cast to number
const newValue =
typeof newValueRaw === 'string'
? newValueRaw.replace(/[^.0-9]/, '')
? newValueRaw.replace(/[^-.0-9]/, '')
: String(newValueRaw)

if (newValue === '.') {
setValue('0.')
} else if (newValue === '-0') {
setValue('0')
} else {
setValue(Number(newValue) >= 0 ? newValue : '0')
isBlowout
? setValue(newValue)
: setValue(Number(newValue) >= 0 ? newValue : '0')
}
}

Expand All @@ -127,7 +146,7 @@ export function ZTipPositionModal(props: ZTipPositionModalProps): JSX.Element {
}

const handleIncrementDecrement = (delta: number): void => {
const prevValue = value === null ? defaultMmFromBottom : Number(value)
const prevValue = value === null ? defaultMm : Number(value)
setIsDefault(false)
handleChange(utils.roundValue(prevValue + delta))
}
Expand All @@ -143,8 +162,8 @@ export function ZTipPositionModal(props: ZTipPositionModalProps): JSX.Element {
const TipPositionInputField = !isDefault && (
<InputField
caption={t('tip_position.caption', {
min: minMmFromBottom,
max: maxMmFromBottom,
min: minMm,
max: maxMm,
})}
className={styles.position_from_bottom_input}
error={errorText}
Expand Down Expand Up @@ -210,9 +229,11 @@ export function ZTipPositionModal(props: ZTipPositionModalProps): JSX.Element {
}}
options={[
{
name: t('tip_position.radio_button.default', {
defaultMmFromBottom,
}),
name: isBlowout
? t('tip_position.radio_button.blowout')
: t('tip_position.radio_button.default', {
defaultMm,
}),
value: 'default',
},
{
Expand Down Expand Up @@ -246,7 +267,18 @@ export function ZTipPositionModal(props: ZTipPositionModalProps): JSX.Element {
) : null}
<TipPositionZAxisViz
mmFromBottom={
value !== null ? Number(value) : defaultMmFromBottom
isBlowout
? undefined
: value !== null
? Number(value)
: defaultMm
}
mmFromTop={
isBlowout
? value !== null
? Number(value)
: defaultMm
: undefined
}
wellDepthMm={wellDepthMm}
/>
Expand Down
Loading

0 comments on commit 048a533

Please sign in to comment.