-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(step-generation, shared-data): pipette collision warnings (#14989)
closes AUTH-19
- Loading branch information
1 parent
9557672
commit b5255b0
Showing
15 changed files
with
768 additions
and
388 deletions.
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
shared-data/js/helpers/__tests__/getFlexSurroundingSlots.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { describe, it, expect } from 'vitest' | ||
import { getFlexSurroundingSlots } from '../getFlexSurroundingSlots' | ||
|
||
describe('getFlexSurroundingSlots', () => { | ||
it('returns slots when slot is D2', () => { | ||
const results = getFlexSurroundingSlots('D2', []) | ||
expect(results).toStrictEqual(['C1', 'C2', 'C3', 'D1', 'D3']) | ||
}) | ||
it('returns slots when selected is a center slot', () => { | ||
const results = getFlexSurroundingSlots('C2', []) | ||
expect(results).toStrictEqual([ | ||
'B1', | ||
'B2', | ||
'B3', | ||
'C1', | ||
'C3', | ||
'D1', | ||
'D2', | ||
'D3', | ||
]) | ||
}) | ||
it('returns slots when selected is a column 3 with staging areas present', () => { | ||
const results = getFlexSurroundingSlots('B3', ['A4']) | ||
expect(results).toStrictEqual(['A2', 'A3', 'A4', 'B2', 'C2', 'C3']) | ||
}) | ||
it('returns slots when selected is a corner, A1', () => { | ||
const results = getFlexSurroundingSlots('A1', ['A4']) | ||
expect(results).toStrictEqual(['A2', 'B1', 'B2']) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import type { DeckSlotId } from '../types' | ||
|
||
const FLEX_GRID = [ | ||
['A1', 'A2', 'A3'], | ||
['B1', 'B2', 'B3'], | ||
['C1', 'C2', 'C3'], | ||
['D1', 'D2', 'D3'], | ||
] | ||
|
||
const LETTER_TO_ROW_MAP: Record<string, number> = { | ||
A: 0, | ||
B: 1, | ||
C: 2, | ||
D: 3, | ||
} | ||
|
||
let COLS = 3 // Initial number of columns in each row | ||
const ROWS = 4 | ||
|
||
const DIRECTIONS = [ | ||
[-1, -1], // NW | ||
[-1, 0], // N | ||
[-1, 1], // NE | ||
[0, -1], // W | ||
[0, 1], // E | ||
[1, -1], // SW | ||
[1, 0], // S | ||
[1, 1], // SE | ||
] | ||
|
||
export const getFlexSurroundingSlots = ( | ||
slot: DeckSlotId, | ||
stagingAreaSlots: DeckSlotId[] | ||
): DeckSlotId[] => { | ||
// Handle staging area slots | ||
if (stagingAreaSlots.length > 0) { | ||
stagingAreaSlots.forEach((stagingSlot, index) => { | ||
if (stagingSlot) { | ||
FLEX_GRID[index].push(stagingSlot) | ||
} | ||
}) | ||
COLS = Math.max(COLS, FLEX_GRID[0].length) // Update COLS to the maximum row length | ||
} | ||
|
||
const letter = slot.charAt(0) | ||
const col = parseInt(slot.charAt(1)) - 1 // Convert the column to a 0-based index | ||
const row = LETTER_TO_ROW_MAP[letter] | ||
|
||
const surroundingSlots: DeckSlotId[] = [] | ||
|
||
// Iterate through both directions | ||
DIRECTIONS.forEach(([dRow, dCol]) => { | ||
const newRow = row + dRow | ||
const newCol = col + dCol | ||
|
||
if (newRow >= 0 && newRow < ROWS && newCol >= 0 && newCol < COLS) { | ||
surroundingSlots.push(FLEX_GRID[newRow][newCol]) | ||
} | ||
}) | ||
|
||
// Filter out any undefined values from the staging area slots that are not added | ||
return surroundingSlots.filter(slot => slot !== undefined) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
167 changes: 167 additions & 0 deletions
167
step-generation/src/__tests__/getIsSafePipetteMovement.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { expect, describe, it } from 'vitest' | ||
import { getIsSafePipetteMovement } from '../utils' | ||
import { | ||
LabwareDefinition2, | ||
TEMPERATURE_MODULE_TYPE, | ||
TEMPERATURE_MODULE_V2, | ||
fixture96Plate, | ||
fixtureP100096V2Specs, | ||
fixtureTiprack1000ul, | ||
fixtureTiprackAdapter, | ||
} from '@opentrons/shared-data' | ||
import { InvariantContext, RobotState } from '../types' | ||
|
||
const mockLabwareId = 'labwareId' | ||
const mockPipId = 'pip' | ||
const mockTiprackId = 'tiprackId' | ||
const mockModule = 'moduleId' | ||
const mockLabware2 = 'labwareId2' | ||
const mockAdapter = 'adapterId' | ||
const mockInvariantProperties: InvariantContext = { | ||
pipetteEntities: { | ||
pip: { | ||
name: 'p1000_96', | ||
id: 'pip', | ||
tiprackDefURI: ['mockDefUri'], | ||
tiprackLabwareDef: [fixtureTiprack1000ul as LabwareDefinition2], | ||
spec: fixtureP100096V2Specs, | ||
}, | ||
}, | ||
labwareEntities: { | ||
[mockLabwareId]: { | ||
id: mockLabwareId, | ||
labwareDefURI: 'mockDefUri', | ||
def: fixture96Plate as LabwareDefinition2, | ||
}, | ||
[mockTiprackId]: { | ||
id: mockTiprackId, | ||
labwareDefURI: 'mockTipUri', | ||
def: fixtureTiprack1000ul as LabwareDefinition2, | ||
}, | ||
[mockAdapter]: { | ||
id: mockAdapter, | ||
labwareDefURI: 'mockAdapterUri', | ||
def: fixtureTiprackAdapter as LabwareDefinition2, | ||
}, | ||
[mockLabware2]: { | ||
id: mockLabware2, | ||
labwareDefURI: 'mockDefUri', | ||
def: fixture96Plate as LabwareDefinition2, | ||
}, | ||
}, | ||
moduleEntities: {}, | ||
additionalEquipmentEntities: {}, | ||
config: { | ||
OT_PD_DISABLE_MODULE_RESTRICTIONS: false, | ||
}, | ||
} | ||
|
||
const mockRobotState: RobotState = { | ||
pipettes: { pip: { mount: 'left' } }, | ||
labware: { [mockLabwareId]: { slot: 'D2' }, [mockTiprackId]: { slot: 'A2' } }, | ||
modules: {}, | ||
tipState: { tipracks: {}, pipettes: {} }, | ||
liquidState: { pipettes: {}, labware: {}, additionalEquipment: {} }, | ||
} | ||
describe('getIsSafePipetteMovement', () => { | ||
it('returns true when the labware id is a trash bin', () => { | ||
const result = getIsSafePipetteMovement( | ||
{ | ||
labware: {}, | ||
pipettes: {}, | ||
modules: {}, | ||
tipState: {}, | ||
liquidState: {}, | ||
} as any, | ||
{ | ||
labwareEntities: {}, | ||
pipetteEntities: {}, | ||
moduleEntities: {}, | ||
additionalEquipmentEntities: { | ||
trashBin: { name: 'trashBin', location: 'A3', id: 'trashBin' }, | ||
}, | ||
config: {} as any, | ||
}, | ||
'mockId', | ||
'mockTrashBin', | ||
'mockTiprackId', | ||
{ x: 0, y: 0, z: 0 } | ||
) | ||
expect(result).toEqual(true) | ||
}) | ||
it('returns false when within pipette extents is false', () => { | ||
const result = getIsSafePipetteMovement( | ||
mockRobotState, | ||
mockInvariantProperties, | ||
mockPipId, | ||
mockLabwareId, | ||
mockTiprackId, | ||
{ x: -12, y: -100, z: 20 } | ||
) | ||
expect(result).toEqual(false) | ||
}) | ||
it('returns true when there are no collisions and a module near it', () => { | ||
mockRobotState.modules = { | ||
[mockModule]: { slot: 'D1', moduleState: {} as any }, | ||
} | ||
mockInvariantProperties.moduleEntities = { | ||
[mockModule]: { | ||
id: mockModule, | ||
type: TEMPERATURE_MODULE_TYPE, | ||
model: TEMPERATURE_MODULE_V2, | ||
}, | ||
} | ||
const result = getIsSafePipetteMovement( | ||
mockRobotState, | ||
mockInvariantProperties, | ||
mockPipId, | ||
mockLabwareId, | ||
mockTiprackId, | ||
{ x: -1, y: 5, z: 20 } | ||
) | ||
expect(result).toEqual(true) | ||
}) | ||
it('returns false when there is a tip that collides', () => { | ||
mockRobotState.tipState.tipracks = { mockTiprackId: { A1: true } } | ||
const result = getIsSafePipetteMovement( | ||
mockRobotState, | ||
mockInvariantProperties, | ||
mockPipId, | ||
mockLabwareId, | ||
mockTiprackId, | ||
{ x: -1, y: 5, z: 0 } | ||
) | ||
expect(result).toEqual(false) | ||
}) | ||
it('returns false when there is a tall module nearby in a diagonal slot with adapter and labware', () => { | ||
mockRobotState.modules = { | ||
[mockModule]: { slot: 'C1', moduleState: {} as any }, | ||
} | ||
mockRobotState.labware = { | ||
[mockLabwareId]: { slot: 'D2' }, | ||
[mockAdapter]: { | ||
slot: mockModule, | ||
}, | ||
[mockLabware2]: { | ||
slot: mockAdapter, | ||
}, | ||
} | ||
mockInvariantProperties.moduleEntities = { | ||
[mockModule]: { | ||
id: mockModule, | ||
type: TEMPERATURE_MODULE_TYPE, | ||
model: TEMPERATURE_MODULE_V2, | ||
}, | ||
} | ||
const result = getIsSafePipetteMovement( | ||
mockRobotState, | ||
mockInvariantProperties, | ||
mockPipId, | ||
mockLabwareId, | ||
mockTiprackId, | ||
{ x: 0, y: 0, z: 0 } | ||
) | ||
expect(result).toEqual(false) | ||
}) | ||
// todo(jr, 4/23/24): add more test cases, test thermocycler collision - i'll do this in a follow up | ||
}) |
Oops, something went wrong.