@opentrons/shared-data 0.1.2-alpha.0 → 0.1.2-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/js/__tests__/__snapshots__/pipettes.test.ts.snap +5969 -0
  2. package/js/__tests__/deckSchemas.test.ts +87 -0
  3. package/js/__tests__/errors.test.js +21 -0
  4. package/js/__tests__/getAreSlotsAdjacent.test.ts +128 -0
  5. package/js/__tests__/getWellNamePerMultiTip.test.ts +162 -0
  6. package/js/__tests__/labwareDefQuirks.test.ts +38 -0
  7. package/js/__tests__/labwareDefSchemaV1.test.ts +82 -0
  8. package/js/__tests__/labwareDefSchemaV2.test.ts +298 -0
  9. package/js/__tests__/moduleAccessors.test.ts +50 -0
  10. package/js/__tests__/moduleSpecsSchema.test.ts +77 -0
  11. package/js/__tests__/pipetteSchemaV2.test.ts +107 -0
  12. package/js/__tests__/pipetteSpecSchemas.test.ts +48 -0
  13. package/js/__tests__/pipettes.test.ts +58 -0
  14. package/js/__tests__/protocolSchemaV4.test.ts +137 -0
  15. package/js/__tests__/protocolSchemaV5.test.ts +137 -0
  16. package/js/__tests__/protocolSchemaV6.test.ts +132 -0
  17. package/js/__tests__/protocolSchemaV7.test.ts +64 -0
  18. package/js/__tests__/protocolValidation.test.ts +83 -0
  19. package/js/__tests__/sortWells.test.ts +40 -0
  20. package/js/__tests__/splitWellsOnColumn.test.ts +21 -0
  21. package/js/__tests__/validateErrors.test.js +34 -0
  22. package/js/constants.ts +405 -0
  23. package/js/cypressUtils.ts +35 -0
  24. package/js/errors.ts +33 -0
  25. package/js/fixtures.ts +156 -0
  26. package/js/getLabware.ts +145 -0
  27. package/js/gripper.ts +29 -0
  28. package/js/helpers/__tests__/getAdapterName.test.ts +31 -0
  29. package/js/helpers/__tests__/getDeckDefFromLoadedLabware.test.ts +12 -0
  30. package/js/helpers/__tests__/getSimplestFlexDeckConfig.test.ts +176 -0
  31. package/js/helpers/__tests__/getVectorDifference.test.ts +25 -0
  32. package/js/helpers/__tests__/getVectorSum.test.ts +21 -0
  33. package/js/helpers/__tests__/labwareInference.test.ts +84 -0
  34. package/js/helpers/__tests__/orderWells.test.ts +87 -0
  35. package/js/helpers/__tests__/parseProtocolData.test.ts +165 -0
  36. package/js/helpers/__tests__/volume.test.ts +108 -0
  37. package/js/helpers/__tests__/wellSets.test.ts +352 -0
  38. package/js/helpers/get96Channel384WellPlateWells.ts +44 -0
  39. package/js/helpers/getAddressableAreasInProtocol.ts +98 -0
  40. package/js/helpers/getLoadedLabwareDefinitionsByUri.ts +30 -0
  41. package/js/helpers/getModuleVizDims.ts +118 -0
  42. package/js/helpers/getOccludedSlotCountForModule.ts +13 -0
  43. package/js/helpers/getSimplestFlexDeckConfig.ts +183 -0
  44. package/js/helpers/getVectorDifference.ts +12 -0
  45. package/js/helpers/getVectorSum.ts +12 -0
  46. package/js/helpers/getWellNamePerMultiTip.ts +86 -0
  47. package/js/helpers/getWellTotalVolume.ts +16 -0
  48. package/js/helpers/index.ts +348 -0
  49. package/js/helpers/labwareInference.ts +92 -0
  50. package/js/helpers/orderWells.ts +69 -0
  51. package/js/helpers/parseProtocolData.ts +141 -0
  52. package/js/helpers/volume.ts +35 -0
  53. package/js/helpers/wellIsRect.ts +6 -0
  54. package/js/helpers/wellSets.ts +155 -0
  55. package/js/index.ts +14 -0
  56. package/js/labwareTools/__tests__/__snapshots__/createIrregularLabware.test.ts.snap +3 -0
  57. package/js/labwareTools/__tests__/__snapshots__/createLabware.test.ts.snap +3 -0
  58. package/js/labwareTools/__tests__/createDefaultDisplayName.test.ts +111 -0
  59. package/js/labwareTools/__tests__/createIrregularLabware.test.ts +226 -0
  60. package/js/labwareTools/__tests__/createLabware.test.ts +147 -0
  61. package/js/labwareTools/index.ts +452 -0
  62. package/js/modules.ts +97 -0
  63. package/js/pipettes.ts +89 -0
  64. package/js/protocols.ts +290 -0
  65. package/js/schema.ts +80 -0
  66. package/js/scripts/generateDeckLayersFromSVG.js +46 -0
  67. package/js/titleCase.ts +30 -0
  68. package/js/types.ts +561 -0
  69. package/lib/js/__tests__/deckSchemas.test.d.ts +1 -0
  70. package/lib/js/__tests__/getAreSlotsAdjacent.test.d.ts +1 -0
  71. package/lib/js/__tests__/getWellNamePerMultiTip.test.d.ts +1 -0
  72. package/lib/js/__tests__/labwareDefQuirks.test.d.ts +1 -0
  73. package/lib/js/__tests__/labwareDefSchemaV1.test.d.ts +1 -0
  74. package/lib/js/__tests__/labwareDefSchemaV2.test.d.ts +1 -0
  75. package/lib/js/__tests__/moduleAccessors.test.d.ts +1 -0
  76. package/lib/js/__tests__/moduleSpecsSchema.test.d.ts +1 -0
  77. package/lib/js/__tests__/pipetteSchemaV2.test.d.ts +1 -0
  78. package/lib/js/__tests__/pipetteSpecSchemas.test.d.ts +1 -0
  79. package/lib/js/__tests__/pipettes.test.d.ts +1 -0
  80. package/lib/js/__tests__/protocolSchemaV4.test.d.ts +1 -0
  81. package/lib/js/__tests__/protocolSchemaV5.test.d.ts +1 -0
  82. package/lib/js/__tests__/protocolSchemaV6.test.d.ts +1 -0
  83. package/lib/js/__tests__/protocolSchemaV7.test.d.ts +1 -0
  84. package/lib/js/__tests__/protocolValidation.test.d.ts +1 -0
  85. package/lib/js/__tests__/sortWells.test.d.ts +1 -0
  86. package/lib/js/__tests__/splitWellsOnColumn.test.d.ts +1 -0
  87. package/lib/js/constants.d.ts +156 -0
  88. package/lib/js/cypressUtils.d.ts +1 -0
  89. package/lib/js/errors.d.ts +13 -0
  90. package/lib/js/fixtures.d.ts +13 -0
  91. package/lib/js/getLabware.d.ts +8 -0
  92. package/lib/js/gripper.d.ts +3 -0
  93. package/lib/js/helpers/__tests__/getAdapterName.test.d.ts +1 -0
  94. package/lib/js/helpers/__tests__/getDeckDefFromLoadedLabware.test.d.ts +1 -0
  95. package/lib/js/helpers/__tests__/getSimplestFlexDeckConfig.test.d.ts +1 -0
  96. package/lib/js/helpers/__tests__/getVectorDifference.test.d.ts +1 -0
  97. package/lib/js/helpers/__tests__/getVectorSum.test.d.ts +1 -0
  98. package/lib/js/helpers/__tests__/labwareInference.test.d.ts +1 -0
  99. package/lib/js/helpers/__tests__/orderWells.test.d.ts +1 -0
  100. package/lib/js/helpers/__tests__/parseProtocolData.test.d.ts +1 -0
  101. package/lib/js/helpers/__tests__/volume.test.d.ts +1 -0
  102. package/lib/js/helpers/__tests__/wellSets.test.d.ts +1 -0
  103. package/lib/js/helpers/get96Channel384WellPlateWells.d.ts +1 -0
  104. package/lib/js/helpers/getAddressableAreasInProtocol.d.ts +4 -0
  105. package/lib/js/helpers/getLoadedLabwareDefinitionsByUri.d.ts +5 -0
  106. package/lib/js/helpers/getModuleVizDims.d.ts +16 -0
  107. package/lib/js/helpers/getOccludedSlotCountForModule.d.ts +2 -0
  108. package/lib/js/helpers/getSimplestFlexDeckConfig.d.ts +14 -0
  109. package/lib/js/helpers/getVectorDifference.d.ts +2 -0
  110. package/lib/js/helpers/getVectorSum.d.ts +2 -0
  111. package/lib/js/helpers/getWellNamePerMultiTip.d.ts +3 -0
  112. package/lib/js/helpers/getWellTotalVolume.d.ts +2 -0
  113. package/lib/js/helpers/index.d.ts +44 -0
  114. package/lib/js/helpers/labwareInference.d.ts +16 -0
  115. package/lib/js/helpers/orderWells.d.ts +2 -0
  116. package/lib/js/helpers/parseProtocolData.d.ts +23 -0
  117. package/lib/js/helpers/volume.d.ts +4 -0
  118. package/lib/js/helpers/wellIsRect.d.ts +3 -0
  119. package/lib/js/helpers/wellSets.d.ts +9 -0
  120. package/lib/js/index.d.ts +14 -0
  121. package/lib/js/labwareTools/__tests__/createDefaultDisplayName.test.d.ts +1 -0
  122. package/lib/js/labwareTools/__tests__/createIrregularLabware.test.d.ts +1 -0
  123. package/lib/js/labwareTools/__tests__/createLabware.test.d.ts +1 -0
  124. package/lib/js/labwareTools/index.d.ts +65 -0
  125. package/lib/js/modules.d.ts +6 -0
  126. package/lib/js/pipettes.d.ts +13 -0
  127. package/lib/js/protocols.d.ts +3 -0
  128. package/lib/js/schema.d.ts +90 -0
  129. package/lib/js/titleCase.d.ts +1 -0
  130. package/lib/js/types.d.ts +398 -0
  131. package/package.json +1 -1
@@ -0,0 +1,98 @@
1
+ import { MOVABLE_TRASH_A3_ADDRESSABLE_AREA } from '../constants'
2
+ import { getAddressableAreaFromSlotId } from '../fixtures'
3
+ import type { AddressableAreaName } from '../../deck'
4
+ import type { ProtocolAnalysisOutput } from '../../protocol'
5
+ import type { CompletedProtocolAnalysis, DeckDefinition } from '../types'
6
+
7
+ export function getAddressableAreasInProtocol(
8
+ protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput,
9
+ deckDef: DeckDefinition
10
+ ): AddressableAreaName[] {
11
+ const { commands, labware } = protocolAnalysis
12
+
13
+ const addressableAreasFromCommands = commands.reduce<AddressableAreaName[]>(
14
+ (acc, command) => {
15
+ if (
16
+ command.commandType === 'moveLabware' &&
17
+ command.params.newLocation !== 'offDeck' &&
18
+ 'slotName' in command.params.newLocation &&
19
+ !acc.includes(
20
+ command.params.newLocation.slotName as AddressableAreaName
21
+ )
22
+ ) {
23
+ const addressableAreaName = getAddressableAreaFromSlotId(
24
+ command.params.newLocation.slotName,
25
+ deckDef
26
+ )?.id
27
+
28
+ if (addressableAreaName == null) {
29
+ return acc
30
+ } else {
31
+ return [...acc, addressableAreaName]
32
+ }
33
+ } else if (
34
+ command.commandType === 'moveLabware' &&
35
+ command.params.newLocation !== 'offDeck' &&
36
+ 'addressableAreaName' in command.params.newLocation &&
37
+ !acc.includes(command.params.newLocation.addressableAreaName)
38
+ ) {
39
+ return [...acc, command.params.newLocation.addressableAreaName]
40
+ } else if (
41
+ (command.commandType === 'loadLabware' ||
42
+ command.commandType === 'loadModule') &&
43
+ command.params.location !== 'offDeck' &&
44
+ 'slotName' in command.params.location &&
45
+ !acc.includes(command.params.location.slotName as AddressableAreaName)
46
+ ) {
47
+ const addressableAreaName = getAddressableAreaFromSlotId(
48
+ command.params.location.slotName,
49
+ deckDef
50
+ )?.id
51
+
52
+ // do not add addressable area name for legacy trash labware
53
+ if (
54
+ addressableAreaName == null ||
55
+ ('loadName' in command.params &&
56
+ command.params.loadName === 'opentrons_1_trash_3200ml_fixed')
57
+ ) {
58
+ return acc
59
+ } else {
60
+ return [...acc, addressableAreaName]
61
+ }
62
+ } else if (
63
+ command.commandType === 'loadLabware' &&
64
+ command.params.location !== 'offDeck' &&
65
+ 'addressableAreaName' in command.params.location &&
66
+ !acc.includes(command.params.location.addressableAreaName)
67
+ ) {
68
+ return [...acc, command.params.location.addressableAreaName]
69
+ } else if (
70
+ command.commandType === 'moveToAddressableArea' &&
71
+ !acc.includes(command.params.addressableAreaName)
72
+ ) {
73
+ return [...acc, command.params.addressableAreaName]
74
+ } else if (
75
+ command.commandType === 'moveToAddressableAreaForDropTip' &&
76
+ !acc.includes(command.params.addressableAreaName)
77
+ ) {
78
+ return [...acc, command.params.addressableAreaName]
79
+ } else {
80
+ return acc
81
+ }
82
+ },
83
+ []
84
+ )
85
+
86
+ // special-case the Flex trash labware load name in A3 for back compatibility with the legacy fixed trash load labware command
87
+ const legacyTrashAddressableArea = labware.some(
88
+ ({ loadName, location }) =>
89
+ loadName === 'opentrons_1_trash_3200ml_fixed' &&
90
+ location !== 'offDeck' &&
91
+ 'slotName' in location &&
92
+ location.slotName === 'A3'
93
+ )
94
+ ? MOVABLE_TRASH_A3_ADDRESSABLE_AREA
95
+ : []
96
+
97
+ return addressableAreasFromCommands.concat(legacyTrashAddressableArea)
98
+ }
@@ -0,0 +1,30 @@
1
+ import { getLabwareDefURI } from '.'
2
+ import type { RunTimeCommand, LabwareDefinition2 } from '..'
3
+
4
+ export interface LabwareDefinitionsByUri {
5
+ [defURI: string]: LabwareDefinition2
6
+ }
7
+
8
+ export function getLoadedLabwareDefinitionsByUri(
9
+ commands: RunTimeCommand[]
10
+ ): LabwareDefinitionsByUri {
11
+ return commands.reduce((acc, command) => {
12
+ if (command.commandType === 'loadLabware') {
13
+ const labwareDef: LabwareDefinition2 | undefined =
14
+ command.result?.definition
15
+ if (labwareDef == null) {
16
+ console.warn(
17
+ `could not find a labware definition in the results of load labware command: ${JSON.stringify(
18
+ command
19
+ )}`
20
+ )
21
+ return acc
22
+ } else {
23
+ const definitionUri = getLabwareDefURI(labwareDef)
24
+ return { ...acc, [definitionUri]: labwareDef }
25
+ }
26
+ } else {
27
+ return acc
28
+ }
29
+ }, {})
30
+ }
@@ -0,0 +1,118 @@
1
+ // NOTE: Ian 2019-10-24 these are by-eye numbers that intentionally
2
+ // ignore the real-life data to emphasize overhangs etc
3
+ import {
4
+ MAGNETIC_MODULE_TYPE,
5
+ TEMPERATURE_MODULE_TYPE,
6
+ THERMOCYCLER_MODULE_TYPE,
7
+ HEATERSHAKER_MODULE_TYPE,
8
+ STD_SLOT_X_DIM as SLOT_X,
9
+ STD_SLOT_Y_DIM as SLOT_Y,
10
+ STD_SLOT_DIVIDER_WIDTH as DIVIDER,
11
+ SPAN7_8_10_11_SLOT,
12
+ MAGNETIC_BLOCK_TYPE,
13
+ } from '../constants'
14
+ import { ModuleType, ModuleOrientation } from '../types'
15
+ // NOTE: all dims are in 'left' orientation. Rotate & transform to obtain 'right' orientation.
16
+
17
+ export interface ModuleVizDims {
18
+ xOffset: number
19
+ yOffset: number
20
+ xDimension: number
21
+ yDimension: number
22
+ childXOffset: number
23
+ childYOffset: number
24
+ childXDimension: number
25
+ childYDimension: number
26
+ }
27
+ const MODULE_VIZ_DIMS: Record<ModuleType, ModuleVizDims> = {
28
+ [MAGNETIC_MODULE_TYPE]: {
29
+ xOffset: -1 * (SLOT_X * 0.2 + DIVIDER),
30
+ yOffset: -1 * DIVIDER,
31
+ xDimension: SLOT_X * 1.2 + DIVIDER * 2,
32
+ yDimension: SLOT_Y + DIVIDER * 2,
33
+ childXOffset: 0,
34
+ childYOffset: 0,
35
+ childXDimension: SLOT_X,
36
+ childYDimension: SLOT_Y,
37
+ },
38
+ [TEMPERATURE_MODULE_TYPE]: {
39
+ xOffset: -1 * (SLOT_X * 0.4 + DIVIDER),
40
+ yOffset: -1 * DIVIDER,
41
+ xDimension: SLOT_X * 1.4 + DIVIDER * 2,
42
+ yDimension: SLOT_Y + DIVIDER * 2,
43
+ childXOffset: 0,
44
+ childYOffset: 0,
45
+ childXDimension: SLOT_X,
46
+ childYDimension: SLOT_Y,
47
+ },
48
+ [THERMOCYCLER_MODULE_TYPE]: {
49
+ xOffset: -8,
50
+ yOffset: 0,
51
+ xDimension: SLOT_X + DIVIDER + 32,
52
+ yDimension: SLOT_Y * 2.4,
53
+ childXOffset: 12,
54
+ childYOffset: SLOT_Y,
55
+ childXDimension: SLOT_X,
56
+ childYDimension: SLOT_Y,
57
+ },
58
+ // TODO(sh, 2022-02-28): This is stubbed out using magdeck dimensions, all of this logic should be deprecated
59
+ [HEATERSHAKER_MODULE_TYPE]: {
60
+ xOffset: -1 * (SLOT_X * 0.2 + DIVIDER),
61
+ yOffset: -1 * DIVIDER,
62
+ xDimension: SLOT_X * 1.2 + DIVIDER * 2,
63
+ yDimension: SLOT_Y + DIVIDER * 2,
64
+ childXOffset: 0,
65
+ childYOffset: 0,
66
+ childXDimension: SLOT_X,
67
+ childYDimension: SLOT_Y,
68
+ },
69
+ // TODO(sh, 2022-02-28): This is stubbed out using magdeck dimensions, all of this logic should be deprecated
70
+ [MAGNETIC_BLOCK_TYPE]: {
71
+ xOffset: -1 * (SLOT_X * 0.2 + DIVIDER),
72
+ yOffset: -1 * DIVIDER,
73
+ xDimension: SLOT_X * 1.2 + DIVIDER * 2,
74
+ yDimension: SLOT_Y + DIVIDER * 2,
75
+ childXOffset: 0,
76
+ childYOffset: 0,
77
+ childXDimension: SLOT_X,
78
+ childYDimension: SLOT_Y,
79
+ },
80
+ }
81
+ export const getModuleVizDims = (
82
+ orientation: ModuleOrientation,
83
+ moduleType: ModuleType
84
+ ): ModuleVizDims => {
85
+ const dims = MODULE_VIZ_DIMS[moduleType]
86
+ if (orientation === 'left') return dims
87
+ // if orientation is 'right', get origins by rotating 180 about slot center
88
+ return {
89
+ ...dims,
90
+ xOffset: SLOT_X - dims.xOffset - dims.xDimension,
91
+ yOffset: SLOT_Y - dims.yOffset - dims.yDimension,
92
+ childXOffset: SLOT_X - dims.childXOffset - dims.childXDimension,
93
+ childYOffset: SLOT_Y - dims.childYOffset - dims.childYDimension,
94
+ }
95
+ }
96
+ type DeckSlot = string
97
+ const LEFT_SIDE_SLOTS: DeckSlot[] = ['1', '4', '7', '10', SPAN7_8_10_11_SLOT]
98
+ export const inferModuleOrientationFromSlot = (
99
+ slot: DeckSlot
100
+ ): ModuleOrientation => {
101
+ // NOTE: defaults to 'left' even for center slots and spanning slots.
102
+ // That's consistent with non-rotatable modules (thermocycler) being
103
+ // considered as 'left' all the time, like we do in MODULE_VIZ_DIMS.
104
+ if (!LEFT_SIDE_SLOTS.includes(slot)) {
105
+ return 'right'
106
+ }
107
+
108
+ return 'left'
109
+ }
110
+
111
+ export const inferModuleOrientationFromXCoordinate = (
112
+ xCoordinate: number
113
+ ): ModuleOrientation => {
114
+ if (xCoordinate === 0) {
115
+ return 'left'
116
+ }
117
+ return 'right'
118
+ }
@@ -0,0 +1,13 @@
1
+ import { OT2_ROBOT_TYPE, THERMOCYCLER_MODULE_TYPE } from '..'
2
+ import type { ModuleType, RobotType } from '..'
3
+
4
+ export function getOccludedSlotCountForModule(
5
+ moduleType: ModuleType,
6
+ robotType: RobotType
7
+ ): number {
8
+ if (moduleType === THERMOCYCLER_MODULE_TYPE) {
9
+ return robotType === OT2_ROBOT_TYPE ? 4 : 2
10
+ } else {
11
+ return 1
12
+ }
13
+ }
@@ -0,0 +1,183 @@
1
+ import { FLEX_ROBOT_TYPE } from '../constants'
2
+ import { getAddressableAreaFromSlotId } from '../fixtures'
3
+ import { getAddressableAreasInProtocol, getDeckDefFromRobotType } from '.'
4
+
5
+ import type { AddressableAreaName, CutoutId } from '../../deck'
6
+ import type { ProtocolAnalysisOutput } from '../../protocol'
7
+ import type {
8
+ CutoutConfig,
9
+ CutoutFixture,
10
+ DeckDefinition,
11
+ DeckConfiguration,
12
+ CompletedProtocolAnalysis,
13
+ } from '../types'
14
+
15
+ export interface CutoutConfigProtocolSpec extends CutoutConfig {
16
+ requiredAddressableAreas: AddressableAreaName[]
17
+ }
18
+
19
+ export const FLEX_SIMPLEST_DECK_CONFIG: DeckConfiguration = [
20
+ { cutoutId: 'cutoutA1', cutoutFixtureId: 'singleLeftSlot' },
21
+ { cutoutId: 'cutoutB1', cutoutFixtureId: 'singleLeftSlot' },
22
+ { cutoutId: 'cutoutC1', cutoutFixtureId: 'singleLeftSlot' },
23
+ { cutoutId: 'cutoutD1', cutoutFixtureId: 'singleLeftSlot' },
24
+ { cutoutId: 'cutoutA2', cutoutFixtureId: 'singleCenterSlot' },
25
+ { cutoutId: 'cutoutB2', cutoutFixtureId: 'singleCenterSlot' },
26
+ { cutoutId: 'cutoutC2', cutoutFixtureId: 'singleCenterSlot' },
27
+ { cutoutId: 'cutoutD2', cutoutFixtureId: 'singleCenterSlot' },
28
+ { cutoutId: 'cutoutA3', cutoutFixtureId: 'singleRightSlot' },
29
+ { cutoutId: 'cutoutB3', cutoutFixtureId: 'singleRightSlot' },
30
+ { cutoutId: 'cutoutC3', cutoutFixtureId: 'singleRightSlot' },
31
+ { cutoutId: 'cutoutD3', cutoutFixtureId: 'singleRightSlot' },
32
+ ]
33
+
34
+ export const FLEX_SIMPLEST_DECK_CONFIG_PROTOCOL_SPEC: CutoutConfigProtocolSpec[] = FLEX_SIMPLEST_DECK_CONFIG.map(
35
+ config => ({ ...config, requiredAddressableAreas: [] })
36
+ )
37
+
38
+ export function getSimplestDeckConfigForProtocol(
39
+ protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null
40
+ ): CutoutConfigProtocolSpec[] {
41
+ // TODO(BC, 2023-11-06): abstract out the robot type
42
+ const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE)
43
+
44
+ const addressableAreas =
45
+ protocolAnalysis != null
46
+ ? getAddressableAreasInProtocol(protocolAnalysis, deckDef)
47
+ : []
48
+ const simplestDeckConfig = addressableAreas.reduce<
49
+ CutoutConfigProtocolSpec[]
50
+ >((acc, addressableArea) => {
51
+ const cutoutFixturesForAddressableArea = getCutoutFixturesForAddressableAreas(
52
+ [addressableArea],
53
+ deckDef.cutoutFixtures
54
+ )
55
+ const cutoutIdForAddressableArea = getCutoutIdForAddressableArea(
56
+ addressableArea,
57
+ cutoutFixturesForAddressableArea
58
+ )
59
+ const cutoutFixturesForCutoutId =
60
+ cutoutIdForAddressableArea != null
61
+ ? getCutoutFixturesForCutoutId(
62
+ cutoutIdForAddressableArea,
63
+ deckDef.cutoutFixtures
64
+ )
65
+ : null
66
+
67
+ const existingCutoutConfig = acc.find(
68
+ cutoutConfig => cutoutConfig.cutoutId === cutoutIdForAddressableArea
69
+ )
70
+
71
+ if (
72
+ existingCutoutConfig != null &&
73
+ cutoutFixturesForCutoutId != null &&
74
+ cutoutIdForAddressableArea != null
75
+ ) {
76
+ const indexOfExistingFixture = cutoutFixturesForCutoutId.findIndex(
77
+ ({ id }) => id === existingCutoutConfig.cutoutFixtureId
78
+ )
79
+ const accIndex = acc.findIndex(
80
+ ({ cutoutId }) => cutoutId === cutoutIdForAddressableArea
81
+ )
82
+ const previousRequiredAAs = acc[accIndex]?.requiredAddressableAreas
83
+ const allNextRequiredAddressableAreas = previousRequiredAAs.includes(
84
+ addressableArea
85
+ )
86
+ ? previousRequiredAAs
87
+ : [...previousRequiredAAs, addressableArea]
88
+ const nextCompatibleCutoutFixture = getSimplestFixtureForAddressableAreas(
89
+ cutoutIdForAddressableArea,
90
+ allNextRequiredAddressableAreas,
91
+ cutoutFixturesForCutoutId
92
+ )
93
+ const indexOfCurrentFixture = cutoutFixturesForCutoutId.findIndex(
94
+ ({ id }) => id === nextCompatibleCutoutFixture?.id
95
+ )
96
+
97
+ if (
98
+ nextCompatibleCutoutFixture != null &&
99
+ indexOfCurrentFixture > indexOfExistingFixture
100
+ ) {
101
+ return [
102
+ ...acc.slice(0, accIndex),
103
+ {
104
+ cutoutId: cutoutIdForAddressableArea,
105
+ cutoutFixtureId: nextCompatibleCutoutFixture.id,
106
+ requiredAddressableAreas: allNextRequiredAddressableAreas,
107
+ },
108
+ ...acc.slice(accIndex + 1),
109
+ ]
110
+ }
111
+ }
112
+ return acc
113
+ }, FLEX_SIMPLEST_DECK_CONFIG_PROTOCOL_SPEC)
114
+
115
+ return simplestDeckConfig
116
+ }
117
+
118
+ export function getCutoutFixturesForAddressableAreas(
119
+ addressableAreas: AddressableAreaName[],
120
+ cutoutFixtures: CutoutFixture[]
121
+ ): CutoutFixture[] {
122
+ return cutoutFixtures.filter(cutoutFixture =>
123
+ Object.values(cutoutFixture.providesAddressableAreas).some(providedAAs =>
124
+ addressableAreas.every(aa => providedAAs.includes(aa))
125
+ )
126
+ )
127
+ }
128
+
129
+ export function getCutoutFixturesForCutoutId(
130
+ cutoutId: CutoutId,
131
+ cutoutFixtures: CutoutFixture[]
132
+ ): CutoutFixture[] {
133
+ return cutoutFixtures.filter(cutoutFixture =>
134
+ cutoutFixture.mayMountTo.some(mayMountTo => mayMountTo.includes(cutoutId))
135
+ )
136
+ }
137
+
138
+ export function getCutoutIdForSlotName(
139
+ slotName: string,
140
+ deckDef: DeckDefinition
141
+ ): CutoutId | null {
142
+ const addressableArea = getAddressableAreaFromSlotId(slotName, deckDef)
143
+ const cutoutIdForSlotName =
144
+ addressableArea != null
145
+ ? getCutoutIdForAddressableArea(
146
+ addressableArea.id,
147
+ deckDef.cutoutFixtures
148
+ )
149
+ : null
150
+
151
+ return cutoutIdForSlotName
152
+ }
153
+
154
+ export function getCutoutIdForAddressableArea(
155
+ addressableArea: AddressableAreaName,
156
+ cutoutFixtures: CutoutFixture[]
157
+ ): CutoutId | null {
158
+ return cutoutFixtures.reduce<CutoutId | null>((acc, cutoutFixture) => {
159
+ const [cutoutId] =
160
+ Object.entries(
161
+ cutoutFixture.providesAddressableAreas
162
+ ).find(([_cutoutId, providedAAs]) =>
163
+ providedAAs.includes(addressableArea)
164
+ ) ?? []
165
+ return (cutoutId as CutoutId) ?? acc
166
+ }, null)
167
+ }
168
+
169
+ export function getSimplestFixtureForAddressableAreas(
170
+ cutoutId: CutoutId,
171
+ requiredAddressableAreas: AddressableAreaName[],
172
+ allCutoutFixtures: CutoutFixture[]
173
+ ): CutoutFixture | null {
174
+ const cutoutFixturesForCutoutId = getCutoutFixturesForCutoutId(
175
+ cutoutId,
176
+ allCutoutFixtures
177
+ )
178
+ const nextCompatibleCutoutFixtures = getCutoutFixturesForAddressableAreas(
179
+ requiredAddressableAreas,
180
+ cutoutFixturesForCutoutId
181
+ )
182
+ return nextCompatibleCutoutFixtures?.[0] ?? null
183
+ }
@@ -0,0 +1,12 @@
1
+ import type { Coordinates } from '../types'
2
+
3
+ export function getVectorDifference(
4
+ pointA: Coordinates,
5
+ pointB: Coordinates
6
+ ): Coordinates {
7
+ return {
8
+ x: pointA.x - pointB.x,
9
+ y: pointA.y - pointB.y,
10
+ z: pointA.z - pointB.z,
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ import type { Coordinates } from '../types'
2
+
3
+ export function getVectorSum(
4
+ pointA: Coordinates,
5
+ pointB: Coordinates
6
+ ): Coordinates {
7
+ return {
8
+ x: pointA.x + pointB.x,
9
+ y: pointA.y + pointB.y,
10
+ z: pointA.z + pointB.z,
11
+ }
12
+ }
@@ -0,0 +1,86 @@
1
+ import range from 'lodash/range'
2
+ import { get96Channel384WellPlateWells } from './get96Channel384WellPlateWells'
3
+ import { getLabwareHasQuirk, orderWells, sortWells } from './index'
4
+ import type { LabwareDefinition2 } from '../types'
5
+
6
+ // TODO Ian 2018-03-13 pull pipette offsets/positions from some pipette definitions data
7
+ const OFFSET_8_CHANNEL = 9 // offset in mm between tips
8
+
9
+ const MULTICHANNEL_TIP_SPAN = OFFSET_8_CHANNEL * (8 - 1) // length in mm from first to last tip of multichannel
10
+
11
+ export function findWellAt(
12
+ labwareDef: LabwareDefinition2,
13
+ x: number,
14
+ y: number
15
+ ): string | null | undefined {
16
+ return Object.keys(labwareDef.wells)
17
+ .sort(sortWells)
18
+ .find((wellName: string) => {
19
+ const well = labwareDef.wells[wellName]
20
+
21
+ if (well.shape === 'circular') {
22
+ return (
23
+ Math.sqrt(Math.pow(x - well.x, 2) + Math.pow(y - well.y, 2)) <
24
+ well.diameter / 2
25
+ )
26
+ }
27
+
28
+ // Not circular, must be a rectangular well
29
+ // For rectangular wells, (x, y) is at the center.
30
+ return (
31
+ Math.abs(x - well.x) < well.xDimension / 2 &&
32
+ Math.abs(y - well.y) < well.yDimension / 2
33
+ )
34
+ })
35
+ }
36
+
37
+ // "topWellName" means well at the "top" of the column we're accessing: usually A row, or B row for 384-format
38
+ export function getWellNamePerMultiTip(
39
+ labwareDef: LabwareDefinition2,
40
+ topWellName: string,
41
+ channels: 8 | 96
42
+ ): string[] | null {
43
+ const topWell = labwareDef.wells[topWellName]
44
+ if (!topWell) {
45
+ console.warn(
46
+ `well "${topWellName}" does not exist in labware ${labwareDef?.namespace}/${labwareDef?.parameters?.loadName}, cannot getWellNamePerMultiTip`
47
+ )
48
+ return null
49
+ }
50
+
51
+ const { x, y } = topWell
52
+ let offsetYTipPositions: number[] = range(0, 8).map(
53
+ tipNo => y - tipNo * OFFSET_8_CHANNEL
54
+ )
55
+ const orderedWells = orderWells(labwareDef.ordering, 't2b', 'l2r')
56
+
57
+ if (getLabwareHasQuirk(labwareDef, 'centerMultichannelOnWells')) {
58
+ // move multichannel up in Y by half the pipette's tip span to center it in the well
59
+ offsetYTipPositions = offsetYTipPositions.map(
60
+ tipPosY => tipPosY + MULTICHANNEL_TIP_SPAN / 2
61
+ )
62
+ }
63
+ // Return null for containers with any undefined wells
64
+ const wellsAccessed = offsetYTipPositions.reduce(
65
+ (acc: string[] | null, tipPosY) => {
66
+ const wellForTip = findWellAt(labwareDef, x, tipPosY)
67
+ if (acc === null || !wellForTip) {
68
+ return null
69
+ }
70
+ return acc.concat(wellForTip)
71
+ },
72
+ []
73
+ )
74
+
75
+ let ninetySixChannelWells = orderedWells
76
+ // special casing 384 well plates to be every other well
77
+ // both on the x and y ases.
78
+ if (orderedWells.length === 384) {
79
+ ninetySixChannelWells = get96Channel384WellPlateWells(
80
+ orderedWells,
81
+ topWellName
82
+ )
83
+ }
84
+
85
+ return channels === 8 ? wellsAccessed : ninetySixChannelWells
86
+ }
@@ -0,0 +1,16 @@
1
+ import type { LabwareDefinition2 } from '../types'
2
+ export const getWellTotalVolume = (
3
+ labwareDef: LabwareDefinition2,
4
+ wellName: string
5
+ ): number | null | undefined => {
6
+ const well = labwareDef.wells[wellName]
7
+
8
+ if (!well) {
9
+ console.warn(
10
+ `No well "${wellName}" found for labware ${labwareDef?.namespace}/${labwareDef?.parameters?.loadName}/${labwareDef?.version}"`
11
+ )
12
+ return null
13
+ }
14
+
15
+ return well.totalLiquidVolume
16
+ }