55 getAddressableAreaFromSlotId ,
66 getDeckDefFromRobotType ,
77 getFlexSurroundingSlots ,
8+ getModuleDef ,
89 getOt2SurroundingSlots ,
910 getPositionFromSlotId ,
1011 OT2_ROBOT_TYPE ,
@@ -23,6 +24,7 @@ import type {
2324 NozzleConfigurationStyle ,
2425 OT2AddressableAreaName ,
2526 PipetteChannels ,
27+ PipetteV2Specs ,
2628 RobotType ,
2729} from '@opentrons/shared-data'
2830import type {
@@ -91,7 +93,8 @@ const getPipetteBoundsAtSpecifiedMoveToPosition = (
9193 pipetteEntity : PipetteEntity ,
9294 tipLength : number ,
9395 wellTargetPoint : Point ,
94- primaryNozzle : string
96+ primaryNozzle : string ,
97+ tipOverlapOnNozzle : number
9598) : Point [ ] => {
9699 const { nozzleMap, nozzleOffset, pipetteBoundingBoxOffsets } =
97100 pipetteEntity . spec
@@ -116,7 +119,6 @@ const getPipetteBoundsAtSpecifiedMoveToPosition = (
116119 const frontY =
117120 wellTargetPoint . y - ( primaryNozzlePoint [ 1 ] - pipetteBoundingBoxFrontYOffset )
118121
119- const tipOverlapOnNozzle = 0
120122 const zNozzles = ( wellTargetPoint . z ?? 0 ) + tipLength - tipOverlapOnNozzle
121123
122124 const backLeftBound = { x : leftX , y : backY , z : zNozzles }
@@ -140,22 +142,28 @@ const getHasOverlappingRectangles = (
140142}
141143
142144const getModuleHeightFromDeckDefinition = (
143- moduleModel : ModuleModel
145+ moduleModel : ModuleModel ,
146+ robotType : RobotType
144147) : number => {
145- const deckDef = getDeckDefFromRobotType ( FLEX_ROBOT_TYPE )
146- const { addressableAreas } = deckDef . locations
147- const moduleAddressableArea = addressableAreas . find ( addressableArea =>
148- addressableArea . id . includes ( moduleModel )
149- )
150- return moduleAddressableArea ?. offsetFromCutoutFixture [ 2 ] ?? 0
148+ if ( robotType === FLEX_ROBOT_TYPE ) {
149+ const deckDef = getDeckDefFromRobotType ( robotType )
150+ const { addressableAreas } = deckDef . locations
151+ const moduleAddressableArea = addressableAreas . find ( addressableArea =>
152+ addressableArea . id . includes ( moduleModel )
153+ )
154+ return moduleAddressableArea ?. offsetFromCutoutFixture [ 2 ] ?? 0
155+ }
156+ // OT-2
157+ return getModuleDef ( moduleModel ) . dimensions . bareOverallHeight
151158}
152159
153160// check the highest Z-point of all items stacked given a deck slot (including modules,
154161// adapters, and modules on adapters)
155162const getHighestZInSlot = (
156163 robotState : RobotState ,
157164 invariantContext : InvariantContext ,
158- slotId : string
165+ slotId : string ,
166+ robotType : RobotType
159167) : number => {
160168 const { modules, labware } = robotState
161169 const { moduleEntities, labwareEntities } = invariantContext
@@ -167,11 +175,12 @@ const getHighestZInSlot = (
167175 )
168176
169177 // if slot has labware, includes labware, adapters, and module
170- if ( largestLabwareStack != null ) {
178+ if ( largestLabwareStack . length > 0 ) {
171179 largestLabwareStack . forEach ( item => {
172180 if ( modules [ item ] != null ) {
173181 totalHeight += getModuleHeightFromDeckDefinition (
174- moduleEntities [ item ] . model
182+ moduleEntities [ item ] . model ,
183+ robotType
175184 )
176185 }
177186 if ( labware [ item ] != null ) {
@@ -181,7 +190,8 @@ const getHighestZInSlot = (
181190 // if slot only has module
182191 } else if ( moduleInSlot != null ) {
183192 totalHeight += getModuleHeightFromDeckDefinition (
184- moduleEntities [ moduleInSlot ] . model
193+ moduleEntities [ moduleInSlot ] . model ,
194+ robotType
185195 )
186196 }
187197 return totalHeight
@@ -241,7 +251,8 @@ const getSlotHasPotentialCollidingObject = (
241251 ? getHighestZInSlot (
242252 robotState ,
243253 invariantContext ,
244- slot . addressableArea . id
254+ slot . addressableArea . id ,
255+ robotType
245256 )
246257 : 0
247258 if ( highestZInSurroundingSlot >= pipetteBounds [ 0 ] ?. z ) {
@@ -344,11 +355,9 @@ export const getIsSafePipetteMovement = (args: {
344355 return true
345356 }
346357
347- const tiprackEntityId = tipState . pipettes [ pipetteId ] ?. tiprackURI
348- const tiprackTipLength =
349- tiprackEntityId != null
350- ? labwareEntities [ tiprackEntityId ] ?. def . parameters . tipLength
351- : 0
358+ const tiprackId = tipState . pipettes [ pipetteId ] ?. tiprackURI
359+ const tiprackEntity = tiprackId != null ? labwareEntities [ tiprackId ] : null
360+ const tiprackTipLength = tiprackEntity ?. def . parameters . tipLength ?? 0
352361 const stagingAreaSlots = Object . values ( stagingAreaEntities ) . map (
353362 stagingArea => stagingArea . location as string
354363 )
@@ -384,11 +393,20 @@ export const getIsSafePipetteMovement = (args: {
384393 if ( ! isWithinPipetteExtents ) {
385394 return false
386395 }
396+ const tipOverlapOnNozzle =
397+ tiprackEntity != null
398+ ? getTipOverlap ( {
399+ pipetteSpecs,
400+ tiprackUri : tiprackEntity . labwareDefURI ,
401+ nozzles : nozzleConfiguration ,
402+ } )
403+ : 0
387404 const pipetteBoundsAtWellLocation = getPipetteBoundsAtSpecifiedMoveToPosition (
388405 pipetteEntity ,
389406 tipLength ,
390407 wellTargetPoint ,
391- primaryNozzle
408+ primaryNozzle ,
409+ tipOverlapOnNozzle
392410 )
393411 const surroundingSlots =
394412 robotType === OT2_ROBOT_TYPE
@@ -582,3 +600,54 @@ export const getTargetTipsFromWellSets = (args: {
582600 return wellSet [ 0 ]
583601 } )
584602}
603+
604+ const getTipOverlap = ( args : {
605+ pipetteSpecs : PipetteV2Specs
606+ tiprackUri : string
607+ nozzles : NozzleConfigurationStyle
608+ } ) : number => {
609+ const { pipetteSpecs, tiprackUri, nozzles } = args
610+ const { channels } = pipetteSpecs
611+ const overlapKey = getOverlapKeyForPipetteSpecs ( channels , nozzles )
612+ const tipOverlaps =
613+ pipetteSpecs . pickUpTipConfigurations . pressFit . configurationsByNozzleMap [
614+ overlapKey
615+ ] ?. default . tipOverlaps
616+
617+ // protect in case we get a bad overlap key
618+ if ( tipOverlaps == null ) {
619+ console . error (
620+ `No tip overlaps found for ${ nozzles } and ${ overlapKey } overlap.`
621+ )
622+ return 0
623+ }
624+ const maxVersion = Math . max (
625+ ...Object . keys ( tipOverlaps ) . map ( version => Number ( version . slice ( 1 ) ) )
626+ )
627+ return (
628+ tipOverlaps [ `v${ maxVersion } ` ] ?. [ tiprackUri ] ??
629+ tipOverlaps [ `v${ maxVersion } ` ] ?. default ??
630+ 0
631+ )
632+ }
633+
634+ const getOverlapKeyForPipetteSpecs = (
635+ channels : PipetteChannels ,
636+ nozzles : NozzleConfigurationStyle
637+ ) : string => {
638+ if ( channels === 1 ) {
639+ return 'SingleA1'
640+ }
641+ if ( channels === 8 ) {
642+ return nozzles === SINGLE ? 'SingleH1' : 'Full'
643+ }
644+ if ( channels === 96 ) {
645+ if ( nozzles === SINGLE ) {
646+ return 'SingleH12'
647+ } else if ( nozzles === COLUMN ) {
648+ return 'Column12'
649+ }
650+ }
651+ // default
652+ return 'Full'
653+ }
0 commit comments