diff --git a/protocol-designer/src/file-data/__tests__/createFile.test.ts b/protocol-designer/src/file-data/__tests__/createFile.test.ts index 4b3ad1103ca..cf651c62733 100644 --- a/protocol-designer/src/file-data/__tests__/createFile.test.ts +++ b/protocol-designer/src/file-data/__tests__/createFile.test.ts @@ -129,7 +129,10 @@ requirements = { } def run(protocol: protocol_api.ProtocolContext): - pass + # Load Labware: + mockPythonName = protocol.load_labware("fixture_trash", "12") + mockPythonName = protocol.load_labware("fixture_tiprack_10_ul", "1") + mockPythonName = protocol.load_labware("fixture_96_plate", "7") `.trimStart() ) }) diff --git a/protocol-designer/src/file-data/__tests__/pythonFile.test.ts b/protocol-designer/src/file-data/__tests__/pythonFile.test.ts index 27fb606865d..9492b9be825 100644 --- a/protocol-designer/src/file-data/__tests__/pythonFile.test.ts +++ b/protocol-designer/src/file-data/__tests__/pythonFile.test.ts @@ -6,13 +6,18 @@ import { MAGNETIC_BLOCK_TYPE, MAGNETIC_BLOCK_V1, OT2_ROBOT_TYPE, + fixture96Plate, + fixtureTiprackAdapter, } from '@opentrons/shared-data' import { + getLoadAdapters, + getLoadLabware, getLoadModules, pythonMetadata, pythonRequirements, } from '../selectors/pythonFile' -import type { TimelineFrame } from '@opentrons/step-generation' +import type { LabwareDefinition2 } from '@opentrons/shared-data' +import type { LabwareEntities, TimelineFrame } from '@opentrons/step-generation' import type { ModuleEntities } from '../../step-forms' describe('pythonMetadata', () => { @@ -64,31 +69,83 @@ requirements = { }) }) +const moduleId = '1' +const moduleId2 = '2' +const moduleId3 = '3' +const mockModuleEntities: ModuleEntities = { + [moduleId]: { + id: moduleId, + model: MAGNETIC_BLOCK_V1, + type: MAGNETIC_BLOCK_TYPE, + pythonName: 'magnetic_block_1', + }, + [moduleId2]: { + id: moduleId2, + model: HEATERSHAKER_MODULE_V1, + type: HEATERSHAKER_MODULE_TYPE, + pythonName: 'heater_shaker_1', + }, + [moduleId3]: { + id: moduleId3, + model: MAGNETIC_BLOCK_V1, + type: MAGNETIC_BLOCK_TYPE, + pythonName: 'magnetic_block_2', + }, +} +const labwareId1 = 'labwareId1' +const labwareId2 = 'labwareId2' +const labwareId3 = 'labwareId3' +const labwareId4 = 'labwareId4' +const labwareId5 = 'labwareId5' + +const mockLabwareEntities: LabwareEntities = { + [labwareId1]: { + id: labwareId1, + labwareDefURI: 'fixture/fixture_flex_96_tiprack_adapter/1', + def: fixtureTiprackAdapter as LabwareDefinition2, + pythonName: 'adapter_1', + }, + [labwareId2]: { + id: labwareId2, + labwareDefURI: 'fixture/fixture_flex_96_tiprack_adapter/1', + def: fixtureTiprackAdapter as LabwareDefinition2, + pythonName: 'adapter_2', + }, + [labwareId3]: { + id: labwareId3, + labwareDefURI: 'fixture/fixture_96_plate/1', + def: fixture96Plate as LabwareDefinition2, + pythonName: 'well_plate_1', + }, + [labwareId4]: { + id: labwareId4, + labwareDefURI: 'fixture/fixture_96_plate/1', + def: fixture96Plate as LabwareDefinition2, + pythonName: 'well_plate_2', + }, + [labwareId5]: { + id: labwareId5, + labwareDefURI: 'fixture/fixture_96_plate/1', + def: fixture96Plate as LabwareDefinition2, + pythonName: 'well_plate_3', + }, +} + +const labwareRobotState: TimelineFrame['labware'] = { + // adapter on a module + [labwareId1]: { slot: moduleId }, + // adapter on a slot + [labwareId2]: { slot: 'B2' }, + // labware on an adapter on a slot + [labwareId3]: { slot: labwareId2 }, + // labware on a module + [labwareId4]: { slot: moduleId3 }, + // labware on a slot + [labwareId5]: { slot: 'C2' }, +} + describe('getLoadModules', () => { it('should generate loadModules', () => { - const moduleId = '1' - const moduleId2 = '2' - const moduleId3 = '3' - const mockModuleEntities: ModuleEntities = { - [moduleId]: { - id: moduleId, - model: MAGNETIC_BLOCK_V1, - type: MAGNETIC_BLOCK_TYPE, - pythonName: 'magnetic_block_1', - }, - [moduleId2]: { - id: moduleId2, - model: HEATERSHAKER_MODULE_V1, - type: HEATERSHAKER_MODULE_TYPE, - pythonName: 'heater_shaker_1', - }, - [moduleId3]: { - id: moduleId3, - model: MAGNETIC_BLOCK_V1, - type: MAGNETIC_BLOCK_TYPE, - pythonName: 'magnetic_block_2', - }, - } const modules: TimelineFrame['modules'] = { [moduleId]: { slot: 'B1', moduleState: {} as any }, [moduleId2]: { slot: 'A1', moduleState: {} as any }, @@ -96,10 +153,42 @@ describe('getLoadModules', () => { } expect(getLoadModules(mockModuleEntities, modules)).toBe( - `# Load Modules: + ` +# Load Modules: magnetic_block_1 = protocol.load_module("magneticBlockV1", "B1") heater_shaker_1 = protocol.load_module("heaterShakerModuleV1", "A1") -magnetic_block_2 = protocol.load_module("magneticBlockV1", "A2")` +magnetic_block_2 = protocol.load_module("magneticBlockV1", "A2")`.trimStart() + ) + }) +}) + +describe('getLoadAdapters', () => { + it('should generate loadAdapters for 2 adapters', () => { + expect( + getLoadAdapters( + mockModuleEntities, + mockLabwareEntities, + labwareRobotState + ) + ).toBe( + ` +# Load Adapters: +adapter_1 = magnetic_block_1.load_adapter("fixture_flex_96_tiprack_adapter") +adapter_2 = protocol.load_adapter("fixture_flex_96_tiprack_adapter", "B2")`.trimStart() + ) + }) +}) + +describe('getLoadLabware', () => { + it('should generate loadLabware for 3 labware', () => { + expect( + getLoadLabware(mockModuleEntities, mockLabwareEntities, labwareRobotState) + ).toBe( + ` +# Load Labware: +well_plate_1 = adapter_2.load_labware("fixture_96_plate") +well_plate_2 = magnetic_block_2.load_labware("fixture_96_plate") +well_plate_3 = protocol.load_labware("fixture_96_plate", "C2")`.trimStart() ) }) }) diff --git a/protocol-designer/src/file-data/selectors/pythonFile.ts b/protocol-designer/src/file-data/selectors/pythonFile.ts index 59d8b747d6f..0643b406439 100644 --- a/protocol-designer/src/file-data/selectors/pythonFile.ts +++ b/protocol-designer/src/file-data/selectors/pythonFile.ts @@ -9,6 +9,7 @@ import { } from '@opentrons/step-generation' import type { InvariantContext, + LabwareEntities, ModuleEntities, TimelineFrame, } from '@opentrons/step-generation' @@ -77,17 +78,77 @@ export function getLoadModules( return hasModules ? `# Load Modules:\n${pythonModules}` : '' } +export function getLoadAdapters( + moduleEntities: ModuleEntities, + labwareEntities: LabwareEntities, + labwareRobotState: TimelineFrame['labware'] +): string { + const adapterEntities = Object.values(labwareEntities).filter(lw => + lw.def.allowedRoles?.includes('adapter') + ) + const pythonAdapters = Object.values(adapterEntities) + .map(adapter => { + const adapterSlot = labwareRobotState[adapter.id].slot + const onModule = moduleEntities[adapterSlot] != null + const location = onModule + ? moduleEntities[adapterSlot].pythonName + : PROTOCOL_CONTEXT_NAME + const slotInfo = onModule ? '' : `, ${formatPyStr(adapterSlot)}` + + return `${adapter.pythonName} = ${location}.load_adapter(${formatPyStr( + adapter.def.parameters.loadName + )}${slotInfo})` + }) + .join('\n') + + return pythonAdapters ? `# Load Adapters:\n${pythonAdapters}` : '' +} + +export function getLoadLabware( + moduleEntities: ModuleEntities, + allLabwareEntities: LabwareEntities, + labwareRobotState: TimelineFrame['labware'] +): string { + const labwareEntities = Object.values(allLabwareEntities).filter( + lw => !lw.def.allowedRoles?.includes('adapter') + ) + const pythonLabware = Object.values(labwareEntities) + .map(labware => { + const labwareSlot = labwareRobotState[labware.id].slot + const onModule = moduleEntities[labwareSlot] != null + const onAdapter = allLabwareEntities[labwareSlot] != null + let location = PROTOCOL_CONTEXT_NAME + if (onAdapter) { + location = allLabwareEntities[labwareSlot].pythonName + } else if (onModule) { + location = moduleEntities[labwareSlot].pythonName + } + const slotInfo = + onModule || onAdapter ? '' : `, ${formatPyStr(labwareSlot)}` + + return `${labware.pythonName} = ${location}.load_labware(${formatPyStr( + labware.def.parameters.loadName + )}${slotInfo})` + }) + .join('\n') + + return pythonLabware ? `# Load Labware:\n${pythonLabware}` : '' +} + export function pythonDefRun( invariantContext: InvariantContext, robotState: TimelineFrame ): string { - const { moduleEntities } = invariantContext - - const loadModules = getLoadModules(moduleEntities, robotState.modules) + const { moduleEntities, labwareEntities } = invariantContext + const { modules, labware } = robotState + const loadModules = getLoadModules(moduleEntities, modules) + const loadAdapters = getLoadAdapters(moduleEntities, labwareEntities, labware) + const loadLabware = getLoadLabware(moduleEntities, labwareEntities, labware) const sections: string[] = [ loadModules, - // loadLabware(), + loadAdapters, + loadLabware, // loadInstruments(), // defineLiquids(), // loadLiquids(), diff --git a/protocol-designer/src/labware-ingred/actions/thunks.ts b/protocol-designer/src/labware-ingred/actions/thunks.ts index 02ed71678e8..061724c8c31 100644 --- a/protocol-designer/src/labware-ingred/actions/thunks.ts +++ b/protocol-designer/src/labware-ingred/actions/thunks.ts @@ -77,7 +77,7 @@ export const createContainer: ( const labwareDef = labwareDefSelectors.getLabwareDefsByURI(state)[ args.labwareDefURI ] - const displayCategory = labwareDef.metadata.displayCategory + const labwareDisplayCategory = labwareDef.metadata.displayCategory const slot = args.slot || getNextAvailableDeckSlot(initialDeckSetup, robotType, labwareDef) @@ -90,6 +90,9 @@ export const createContainer: ( : null if (adapterId != null && args.adapterUnderLabwareDefURI != null) { + const adapterDef = labwareDefSelectors.getLabwareDefsByURI(state)[ + args.adapterUnderLabwareDefURI + ] dispatch({ type: 'CREATE_CONTAINER', payload: { @@ -97,7 +100,7 @@ export const createContainer: ( labwareDefURI: args.adapterUnderLabwareDefURI, id: adapterId, slot, - displayCategory, + displayCategory: adapterDef.metadata.displayCategory, }, }) dispatch({ @@ -106,13 +109,13 @@ export const createContainer: ( ...args, id, slot: adapterId, - displayCategory, + displayCategory: labwareDisplayCategory, }, }) } else { dispatch({ type: 'CREATE_CONTAINER', - payload: { ...args, id, slot, displayCategory }, + payload: { ...args, id, slot, displayCategory: labwareDisplayCategory }, }) } if (isTiprack) {