Skip to content
Open
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
599842c
WIP skeleton
TamarZanzouri Nov 4, 2025
beeadfb
state update WIP
TamarZanzouri Nov 4, 2025
024c8aa
renaming module state to match step gen
TamarZanzouri Nov 4, 2025
244a3cc
added tests and started with open latch command
TamarZanzouri Nov 4, 2025
acda6cf
renaming methods and break for unsafe commands
TamarZanzouri Nov 5, 2025
5b15343
test fixes
TamarZanzouri Nov 5, 2025
7c77fc1
added tests for fill
TamarZanzouri Nov 6, 2025
5c6829c
make sure the count is less than max number
TamarZanzouri Nov 6, 2025
7d79fa5
check js
TamarZanzouri Nov 6, 2025
8ed53be
getModuleMaxFillHeight
TamarZanzouri Nov 6, 2025
4fdd56e
added tests and renaming
TamarZanzouri Nov 7, 2025
0f59fbb
wire up max count for stack
TamarZanzouri Nov 7, 2025
cc0e526
getHeightOfLabwareStackFromDefinitions
TamarZanzouri Nov 7, 2025
d910e9a
logic for getHeightOfLabwareStackFromDefinitions
TamarZanzouri Nov 7, 2025
90bbdec
tests for getHeightOfLabwareStackFromDefinitions
TamarZanzouri Nov 7, 2025
372c4b9
linting
TamarZanzouri Nov 7, 2025
1c35096
stack of stored labware defs
TamarZanzouri Nov 7, 2025
887318d
WIP forFlexStackerRetrieve
TamarZanzouri Nov 7, 2025
1e27974
retrieve WIP
TamarZanzouri Nov 9, 2025
3127502
is slot taken
TamarZanzouri Nov 10, 2025
2d17cfc
labware tests in slot
TamarZanzouri Nov 10, 2025
fe64f50
getIsSlotValid and WIP getIsSlotOccupied
TamarZanzouri Nov 10, 2025
6bb1dbc
renaming and fixed logic for retrieve
TamarZanzouri Nov 11, 2025
a0d38bc
raise tests
TamarZanzouri Nov 12, 2025
2bf5d31
logic test for retrieve
TamarZanzouri Nov 12, 2025
c30b7c0
fixed failing test
TamarZanzouri Nov 12, 2025
97b9013
moved slot dec to symbolic helpers
TamarZanzouri Nov 12, 2025
611e885
WIP getIsSlotOccupied use LabwareLocation
TamarZanzouri Nov 12, 2025
2cb95d3
import fix and labwareLocation fix
TamarZanzouri Nov 12, 2025
8c2c22d
labwareIdsInStacker
TamarZanzouri Nov 12, 2025
d64310f
renaming and orginizing
TamarZanzouri Nov 12, 2025
d5e204e
mocking fix
TamarZanzouri Nov 13, 2025
96fc952
pythonFileUtils set_store_labware
TamarZanzouri Nov 13, 2025
2bc04e6
WIP add flex stacker to tests
TamarZanzouri Nov 13, 2025
1ed2298
added tests
TamarZanzouri Nov 14, 2025
2262691
lint
TamarZanzouri Nov 14, 2025
501e409
logic fix for set stored labware
TamarZanzouri Nov 14, 2025
69daacc
cleanup
TamarZanzouri Nov 14, 2025
63b456c
cleanup
TamarZanzouri Nov 14, 2025
e134190
lint fixes
TamarZanzouri Nov 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Command models to engage a user to empty a Flex Stacker."""
"""Command models to engage a user to fill a Flex Stacker."""

from __future__ import annotations
from typing import Optional, Literal, TYPE_CHECKING, Annotated
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Command models to retrieve a labware from a Flex Stacker."""
"""Command models to store a labware in a Flex Stacker."""

from __future__ import annotations
from typing import Optional, Literal, TYPE_CHECKING, Type, Union, cast
Expand Down
8 changes: 8 additions & 0 deletions protocol-designer/src/step-forms/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@ const ABSORBANCE_READER_INITIAL_STATE: AbsorbanceReaderState = {
}
const FLEX_STACKER_INITIAL_STATE: FlexStackerModuleState = {
type: FLEX_STACKER_MODULE_TYPE,
latchOpen: null,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

maxPoolCount: 0,
storedLabwareDetails: null,
shuttlePosition: 'home',
labwareInStacker: null,
labwareInShuttle: null,
labwareRetrieved: null,
labwareStored: null,
}

const MODULE_INITIAL_STATES_MAP: Record<
Expand Down
10 changes: 9 additions & 1 deletion protocol-designer/src/step-forms/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
CutoutId,
FLEX_STACKER_MODULE_TYPE,
FlexModuleCutoutFixtureId,
FlexStackerSetStoredLabwareParams,
HEATERSHAKER_MODULE_TYPE,
MAGNETIC_BLOCK_TYPE,
MAGNETIC_MODULE_TYPE,
Expand Down Expand Up @@ -71,7 +72,14 @@ export interface MagneticBlockState {

export interface FlexStackerModuleState {
type: typeof FLEX_STACKER_MODULE_TYPE
// TODO: extend this state
latchOpen: boolean | null
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

maxPoolCount: number
storedLabwareDetails: FlexStackerSetStoredLabwareParams | null
shuttlePosition: 'home' | 'retrieved' | 'stored'
labwareInStacker: string[] | null
labwareInShuttle: number | null
labwareRetrieved: number | null
labwareStored: number | null
}
export type InitializationMode = 'single' | 'multi'
export interface Initialization {
Expand Down
48 changes: 27 additions & 21 deletions shared-data/command/types/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,17 +461,19 @@ interface StackerStoredLabwareDefinitionURIs {
lidLabwareURI?: string | null
}

export interface FlexStackerSetStoredLabwareParams {
moduleId: string
initialCount?: number | null
initialStoredLabware?: FlexStackerStoredLabwareGroup[] | null
primaryLabware: FlexStackerStoredLabwareDetails
lidLabware: FlexStackerStoredLabwareDetails | null
adapterLabware: FlexStackerStoredLabwareDetails | null
}

export interface FlexStackerSetStoredLabwareCreateCommand
extends CommonCommandCreateInfo {
commandType: 'flexStacker/setStoredLabware'
params: {
moduleId: string
initialCount?: number | null
initialStoredLabware?: FlexStackerStoredLabwareGroup[] | null
primaryLabware: FlexStackerStoredLabwareDetails
lidLabware: FlexStackerStoredLabwareDetails | null
adapterLabware: FlexStackerStoredLabwareDetails | null
}
params: FlexStackerSetStoredLabwareParams
}

export interface FlexStackerSetStoredLabwareRunTimeCommand
Expand Down Expand Up @@ -502,25 +504,29 @@ export interface FlexStackerStoreCreateCommand extends CommonCommandCreateInfo {
}
}

export interface FlexStackerFillParams {
moduleId: string
strategy: 'manualWithPause' | 'logical'
message?: string
count?: number
labwareToStore?: FlexStackerStoredLabwareGroup[]
}

export interface FlexStackerFillCreateCommand extends CommonCommandCreateInfo {
commandType: 'flexStacker/fill'
params: {
moduleId: string
strategy: 'manualWithPause' | 'logical'
message?: string
count?: number
labwareToStore?: FlexStackerStoredLabwareGroup[]
}
params: FlexStackerFillParams
}

export interface FlexStackerEmptyParams {
moduleId: string
strategy: 'manualWithPause' | 'logical'
message?: string
count?: number
}

export interface FlexStackerEmptyCreateCommand extends CommonCommandCreateInfo {
commandType: 'flexStacker/empty'
params: {
moduleId: string
strategy: 'manualWithPause' | 'logical'
message?: string
count?: number
}
params: FlexStackerEmptyParams
}

export interface FlexStackerPrepareShuttleCreateCommand
Expand Down
2 changes: 2 additions & 0 deletions shared-data/js/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,7 @@ export function getFixtureDisplayName(
}
}

// TODO: Move to helpers/deckDeclarationHelpers.ts
export const STANDARD_OT2_SLOTS: AddressableAreaName[] = [
ADDRESSABLE_AREA_1,
ADDRESSABLE_AREA_2,
Expand All @@ -882,6 +883,7 @@ export const STANDARD_OT2_SLOTS: AddressableAreaName[] = [
ADDRESSABLE_AREA_11,
]

// TODO: Move to helpers
export const STANDARD_FLEX_SLOTS: AddressableAreaName[] = [
A1_ADDRESSABLE_AREA,
A2_ADDRESSABLE_AREA,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { describe, expect, it } from 'vitest'

import {
fixtureTiprack1000ul,
FLEX_STACKER_MODULE_V1,
getSchema2Dimensions,
MAGNETIC_MODULE_V1,
} from '../..'
import {
getHeightOfLabwareStackFromDefinitions,
getLabwareOverlapOffset,
getModuleMaxFillHeight,
getStackerMaxPoolCountByHeight,
} from '../getFlexStackerHardwareProps'

import type { LabwareDefinition2 } from '../..'

describe('getModuleMaxFillHeight()', () => {
it('should return the max fill height for a given module model', () => {
expect(getModuleMaxFillHeight(FLEX_STACKER_MODULE_V1)).toBe(612.75)
})
})

describe('getStackerMaxPoolCountByHeight()', () => {
it('should return the max pool count by height for a given module model', () => {
expect(getStackerMaxPoolCountByHeight(FLEX_STACKER_MODULE_V1, 100, 0)).toBe(
6
)
})

it('should throw an error if the module model is invalid', () => {
expect(() =>
getStackerMaxPoolCountByHeight(MAGNETIC_MODULE_V1, 100, 0)
).toThrow(
'Invalid module model for max pool count by height: magneticModuleV1'
)
})
})

describe('getLabwareOverlapOffset()', () => {
const mockLabwareDefinition = fixtureTiprack1000ul as LabwareDefinition2

it('should return the labware overlap offset for a given module model', () => {
const result = getLabwareOverlapOffset(
FLEX_STACKER_MODULE_V1,
mockLabwareDefinition,
'labware-name'
)
expect(result).toStrictEqual({ x: 0, y: 0, z: 0 })
})

it('should throw an error if the module model is invalid', () => {
expect(() =>
getLabwareOverlapOffset(
MAGNETIC_MODULE_V1,
mockLabwareDefinition,
'labware-name'
)
).toThrow(
'Invalid module model for labware overlap offset: magneticModuleV1'
)
})
})

describe('getHeightOfLabwareStackFromDefinitions()', () => {
it('should return the height of a stack of labware from definitions', () => {
const mockLabwareDefinition = fixtureTiprack1000ul as LabwareDefinition2
const result = getHeightOfLabwareStackFromDefinitions([
mockLabwareDefinition,
])
expect(result).toBe(getSchema2Dimensions(mockLabwareDefinition).zDimension)
})

it('should return 0 if the definitions are empty', () => {
const result = getHeightOfLabwareStackFromDefinitions([])
expect(result).toBe(0)
})

it('should return the height of a stack of labware from definitions', () => {
const mockLabwareDefinition = fixtureTiprack1000ul as LabwareDefinition2
const result = getHeightOfLabwareStackFromDefinitions([
mockLabwareDefinition,
mockLabwareDefinition,
])
expect(result).toBe(
getSchema2Dimensions(mockLabwareDefinition).zDimension * 2
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { beforeEach, describe, expect, it } from 'vitest'

import { STANDARD_FLEX_SLOTS, STANDARD_OT2_SLOTS } from '../../fixtures'
import { getIsValidSlotName } from '../symbolicPositionHelpers'

describe('getIsSlotValid', () => {
beforeEach(() => {})

it('returns true for a valid slot', () => {
STANDARD_FLEX_SLOTS.forEach(slot => {
expect(getIsValidSlotName(slot)).toBe(true)
})
STANDARD_OT2_SLOTS.forEach(slot => {
expect(getIsValidSlotName(slot)).toBe(true)
})
})
it('returns false for an invalid slot', () => {
expect(getIsValidSlotName('13')).toBe(false)
})
it('returns false for an invalid slot', () => {
expect(getIsValidSlotName('A5')).toBe(false)
})
})
77 changes: 77 additions & 0 deletions shared-data/js/helpers/getFlexStackerHardwareProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { FLEX_STACKER_MODULE_V1 } from '../constants'
import { getModuleDef } from '../modules'
import { getSchema2Dimensions } from './positionMath'

import type { LabwareDefinition, ModuleModel, Vector3D } from '../types'

export const getModuleMaxFillHeight = (model: ModuleModel): number => {
if (model === FLEX_STACKER_MODULE_V1) {
return (
getModuleDef(FLEX_STACKER_MODULE_V1).dimensions.maxStackerFillHeight ?? 0
)
}
throw new Error(`Invalid module model for max fill height: ${model}`)
}

export const getStackerMaxPoolCountByHeight = (
model: ModuleModel,
poolHeight: number,
poolOverlap: number
): number => {
if (model === FLEX_STACKER_MODULE_V1) {
const maxFillHeight = getModuleMaxFillHeight(model)
if (maxFillHeight <= 0) {
throw new Error(
`Invalid max fill height for ${model}: ${maxFillHeight} must be greater than 0`
)
}
return Math.floor(
(maxFillHeight - poolOverlap) / (poolHeight - poolOverlap)
)
}
throw new Error(`Invalid module model for max pool count by height: ${model}`)
}

export const getLabwareOverlapOffset = (
model: ModuleModel,
definition: LabwareDefinition,
belowLabwareName: string
): Vector3D => {
if (model !== FLEX_STACKER_MODULE_V1) {
throw new Error(`Invalid module model for labware overlap offset: ${model}`)
}
if (
belowLabwareName in Object.keys(definition.stackingOffsetWithLabware ?? {})
) {
return (
definition.stackingOffsetWithLabware?.[belowLabwareName] ?? {
x: 0,
y: 0,
z: 0,
}
)
}
return definition.stackingOffsetWithLabware?.default ?? { x: 0, y: 0, z: 0 }
}

export const getHeightOfLabwareStackFromDefinitions = (
definitions: LabwareDefinition[]
): number => {
if (definitions.length === 0) {
return 0
}
let total_height = 0.0
let upper_def: LabwareDefinition = definitions[0]
for (const lower_def of definitions.slice(1)) {
const overlap = getLabwareOverlapOffset(
FLEX_STACKER_MODULE_V1,
upper_def,
lower_def.parameters.loadName
).z
total_height += getSchema2Dimensions(upper_def).zDimension - overlap
upper_def = lower_def
}
return total_height + getSchema2Dimensions(upper_def).zDimension
}
// export const getModuleMaxRetrievableHeight = (model: ModuleModel): number =>
// getModuleDef(model).dimensions.maxStackerRetrievableHeight ?? 0
1 change: 1 addition & 0 deletions shared-data/js/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export * from './matrixMath'
export * from './getLoadedLabwareDefinitionsByUri'
export * from './getFixedTrashLabwareDefinition'
export * from './getOccludedSlotCountForModule'
export * from './getFlexStackerHardwareProps'
export * from './labwareInference'
export * from './linearInterpolate'
export * from './liquidClasses'
Expand Down
15 changes: 14 additions & 1 deletion shared-data/js/helpers/symbolicPositionHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { STANDARD_FLEX_SLOTS, STANDARD_OT2_SLOTS } from '../fixtures'

import type {
LabwareLocation,
OnDeckLabwareLocation,
} from '../../command/types/setup'
import type { AddressableAreaName } from '../../js'
import type { AddressableAreaName } from '../../deck'

export const changeAnyUseOfMeToPreserveStructure_thisIsAnOffDeckLocationInASlotName =
(quoteUnquoteSlotName: string): boolean =>
Expand Down Expand Up @@ -41,3 +43,14 @@ export const locationIsOnAddressableArea = (
labwareLocation: LabwareLocation
): labwareLocation is { addressableAreaName: AddressableAreaName } =>
locationIsOnDeck(labwareLocation) && 'addressableAreaName' in labwareLocation

export const getIsValidSlotName = (slot: string): boolean => {
return (
STANDARD_OT2_SLOTS.includes(slot as AddressableAreaName) ||
STANDARD_FLEX_SLOTS.includes(slot as AddressableAreaName) ||
slot === 'A4' ||
slot === 'B4' ||
slot === 'C4' ||
slot === 'D4'
)
}
2 changes: 2 additions & 0 deletions shared-data/js/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,8 @@ export interface ModuleDimensions {
labwareInterfaceXDimension?: number
labwareInterfaceYDimension?: number
lidHeight?: number
maxStackerFillHeight?: number
maxStackerRetrievableHeight?: number
}

export interface ModuleCalibrationPoint {
Expand Down
Loading
Loading