Skip to content

Commit e04b24a

Browse files
authored
feat(shared-data, step-generation): introduce lid stack loading and lid movement (#19021)
This PR adds functionality to step generation's `moveLabware` command creator to move lids to and from other labware, lid stacks, or slots. Labware incompatibility raises a step generation error creator. I also add logic to load lid stacks in `fileCreator`. Note that rendering of lids on top of parent labware does not seem to be wired up yet. Closes AUTH-2008
1 parent da5d225 commit e04b24a

File tree

7 files changed

+619
-66
lines changed

7 files changed

+619
-66
lines changed

shared-data/js/getLabware.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,6 @@ export function getWellPropsForSVGLabwareV1(
170170
y: _getSvgYValueForWell(def, wellDef) + yCorrection,
171171
}))
172172
}
173+
174+
export const getIsLid = (labwareDef: LabwareDefinition): boolean =>
175+
labwareDef.allowedRoles?.includes('lid') ?? false

step-generation/src/__tests__/moveLabware.test.ts

Lines changed: 296 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ describe('moveLabware', () => {
203203
...invariantContext,
204204
labwareEntities: {
205205
...invariantContext.labwareEntities,
206+
[SOURCE_LABWARE]: {
207+
...invariantContext.labwareEntities[SOURCE_LABWARE],
208+
pythonName: 'mock_source_plate',
209+
def: {
210+
...invariantContext.labwareEntities[SOURCE_LABWARE].def,
211+
compatibleParentLabware: ['fixture_flex_96_tiprack_adapter'],
212+
} as LabwareDefinition2,
213+
},
206214
[DEST_LABWARE]: {
207215
def: fixtureTiprackAdapter as LabwareDefinition2,
208216
pythonName: 'mock_dest_adapter',
@@ -342,6 +350,7 @@ describe('moveLabware', () => {
342350
strategy: 'usingGripper',
343351
newLocation: { slotName: '1' },
344352
} as MoveLabwareParams
353+
345354
robotState = {
346355
...robotState,
347356
...robotState.labware,
@@ -358,10 +367,17 @@ describe('moveLabware', () => {
358367
...invariantContext,
359368
labwareEntities: {
360369
...invariantContext.labwareEntities,
370+
[SOURCE_LABWARE]: {
371+
...invariantContext.labwareEntities[SOURCE_LABWARE],
372+
def: {
373+
...invariantContext.labwareEntities[SOURCE_LABWARE].def,
374+
compatibleParentLabware: ['fixture_12_trough'],
375+
} as LabwareDefinition2,
376+
pythonName: 'mock_source_plate',
377+
},
361378
stackingLabware: {
362379
def: {
363380
...fixture12Trough,
364-
compatibleParentLabware: ['fixture_96_plate'],
365381
} as LabwareDefinition2,
366382
} as any,
367383
},
@@ -389,13 +405,25 @@ describe('moveLabware', () => {
389405

390406
robotState = {
391407
...state,
392-
393408
labware: {
394409
...state.labware,
395410
mockLabwareId: {
396411
stack: ['mockLabwareId', HEATER_SHAKER_ID, HEATER_SHAKER_SLOT],
397412
},
398413
},
414+
modules: {
415+
...state.modules,
416+
[HEATER_SHAKER_ID]: {
417+
...state.modules[HEATER_SHAKER_ID],
418+
slot: HEATER_SHAKER_SLOT,
419+
moduleState: {
420+
type: HEATERSHAKER_MODULE_TYPE,
421+
latchOpen: true,
422+
targetSpeed: null,
423+
targetTemp: null,
424+
},
425+
},
426+
},
399427
}
400428
invariantContext = {
401429
...invariantContext,
@@ -529,6 +557,16 @@ describe('moveLabware', () => {
529557
const HEATER_SHAKER_ID = 'heaterShakerId'
530558
const HEATER_SHAKER_SLOT = 'A1'
531559

560+
invariantContext = {
561+
...invariantContext,
562+
moduleEntities: {
563+
[HEATER_SHAKER_ID]: {
564+
...invariantContext.moduleEntities[HEATER_SHAKER_ID],
565+
pythonName: 'mock_heater_shaker',
566+
},
567+
},
568+
}
569+
532570
robotState = {
533571
...state,
534572
modules: {
@@ -561,6 +599,22 @@ describe('moveLabware', () => {
561599
const HEATER_SHAKER_SLOT = 'A1'
562600
const ADAPTER_ID = 'adapterId'
563601

602+
invariantContext = {
603+
...invariantContext,
604+
labwareEntities: {
605+
...invariantContext.labwareEntities,
606+
[ADAPTER_ID]: {
607+
...invariantContext.labwareEntities[ADAPTER_ID],
608+
def: {
609+
parameters: {
610+
loadName: 'opentrons_96_wellplate_200ul_pcr_full_skirt',
611+
},
612+
} as LabwareDefinition2,
613+
pythonName: 'mock_adapter',
614+
},
615+
},
616+
}
617+
564618
robotState = {
565619
...state,
566620
labware: {
@@ -598,6 +652,16 @@ describe('moveLabware', () => {
598652
const HEATER_SHAKER_ID = 'heaterShakerId'
599653
const HEATER_SHAKER_SLOT = 'A1'
600654

655+
invariantContext = {
656+
...invariantContext,
657+
moduleEntities: {
658+
[HEATER_SHAKER_ID]: {
659+
...invariantContext.moduleEntities[HEATER_SHAKER_ID],
660+
pythonName: 'mock_heater_shaker',
661+
},
662+
},
663+
}
664+
601665
robotState = {
602666
...state,
603667
modules: {
@@ -778,4 +842,234 @@ describe('moveLabware', () => {
778842
type: 'PIPETTE_HAS_TIP',
779843
})
780844
})
845+
846+
it('should return a move_lid command when moving a lid from a stack to a stack', () => {
847+
invariantContext = {
848+
...invariantContext,
849+
labwareEntities: {
850+
...invariantContext.labwareEntities,
851+
[SOURCE_LABWARE]: {
852+
...invariantContext.labwareEntities[SOURCE_LABWARE],
853+
def: {
854+
...invariantContext.labwareEntities[SOURCE_LABWARE].def,
855+
allowedRoles: ['lid'],
856+
compatibleParentLabware: ['fixture_96_plate'],
857+
} as LabwareDefinition2,
858+
},
859+
stackingLabware: {
860+
def: {
861+
...invariantContext.labwareEntities[SOURCE_LABWARE].def,
862+
allowedRoles: ['lid'],
863+
} as LabwareDefinition2,
864+
} as any,
865+
},
866+
} as InvariantContext
867+
868+
robotState = {
869+
...robotState,
870+
labware: {
871+
...robotState.labware,
872+
[SOURCE_LABWARE]: {
873+
...robotState.labware[SOURCE_LABWARE],
874+
stack: [SOURCE_LABWARE, 'A2'],
875+
},
876+
stackingLabware: {
877+
stack: ['stackingLabware', 'A1'],
878+
},
879+
},
880+
}
881+
882+
const params = {
883+
labwareId: SOURCE_LABWARE,
884+
newLocation: { slotName: 'A1' },
885+
strategy: 'usingGripper',
886+
} as MoveLabwareParams
887+
888+
const result = moveLabware(params, invariantContext, robotState)
889+
expect(getSuccessResult(result).python).toBe(
890+
`protocol.move_lid("A2", "A1", use_gripper=True)`
891+
)
892+
})
893+
894+
it('should return a move_lid command when moving a lid from a stack to a slot', () => {
895+
invariantContext = {
896+
...invariantContext,
897+
labwareEntities: {
898+
...invariantContext.labwareEntities,
899+
[SOURCE_LABWARE]: {
900+
...invariantContext.labwareEntities[SOURCE_LABWARE],
901+
def: {
902+
...invariantContext.labwareEntities[SOURCE_LABWARE].def,
903+
allowedRoles: ['lid'],
904+
} as LabwareDefinition2,
905+
},
906+
},
907+
} as InvariantContext
908+
909+
robotState = {
910+
...robotState,
911+
labware: {
912+
...robotState.labware,
913+
[SOURCE_LABWARE]: {
914+
...robotState.labware[SOURCE_LABWARE],
915+
stack: [SOURCE_LABWARE, 'A2'],
916+
},
917+
},
918+
}
919+
920+
const params = {
921+
labwareId: SOURCE_LABWARE,
922+
newLocation: { slotName: 'A1' },
923+
strategy: 'usingGripper',
924+
} as MoveLabwareParams
925+
926+
const result = moveLabware(params, invariantContext, robotState)
927+
expect(getSuccessResult(result).python).toBe(
928+
`protocol.move_lid("A2", "A1", use_gripper=True)`
929+
)
930+
})
931+
932+
it('should return a move_lid command when moving a lid from a labware to a slot', () => {
933+
invariantContext = {
934+
...invariantContext,
935+
labwareEntities: {
936+
...invariantContext.labwareEntities,
937+
[SOURCE_LABWARE]: {
938+
...invariantContext.labwareEntities[SOURCE_LABWARE],
939+
def: {
940+
...invariantContext.labwareEntities[SOURCE_LABWARE].def,
941+
allowedRoles: ['lid'],
942+
} as LabwareDefinition2,
943+
},
944+
stackingLabware: {
945+
def: {
946+
...fixture12Trough,
947+
} as LabwareDefinition2,
948+
pythonName: 'stacking_labware',
949+
} as any,
950+
},
951+
} as InvariantContext
952+
953+
robotState = {
954+
...robotState,
955+
labware: {
956+
...robotState.labware,
957+
stackingLabware: {
958+
stack: ['stackingLabware', '1'],
959+
},
960+
[SOURCE_LABWARE]: {
961+
...robotState.labware[SOURCE_LABWARE],
962+
stack: [SOURCE_LABWARE, 'stackingLabware', 'A2'],
963+
},
964+
},
965+
}
966+
967+
const params = {
968+
labwareId: SOURCE_LABWARE,
969+
newLocation: { slotName: 'A1' },
970+
strategy: 'usingGripper',
971+
} as MoveLabwareParams
972+
973+
const result = moveLabware(params, invariantContext, robotState)
974+
expect(getSuccessResult(result).python).toBe(
975+
`protocol.move_lid(stacking_labware, "A1", use_gripper=True)`
976+
)
977+
})
978+
979+
it('should return a move_lid command when moving a lid from a stack to a compatible labware', () => {
980+
invariantContext = {
981+
...invariantContext,
982+
labwareEntities: {
983+
...invariantContext.labwareEntities,
984+
[SOURCE_LABWARE]: {
985+
...invariantContext.labwareEntities[SOURCE_LABWARE],
986+
def: {
987+
...invariantContext.labwareEntities[SOURCE_LABWARE].def,
988+
allowedRoles: ['lid'],
989+
compatibleParentLabware: ['fixture_12_trough'],
990+
} as LabwareDefinition2,
991+
},
992+
stackingLabware: {
993+
def: {
994+
...fixture12Trough,
995+
} as LabwareDefinition2,
996+
pythonName: 'stacking_labware',
997+
} as any,
998+
},
999+
} as InvariantContext
1000+
1001+
robotState = {
1002+
...robotState,
1003+
labware: {
1004+
...robotState.labware,
1005+
stackingLabware: {
1006+
stack: ['stackingLabware', 'A1'],
1007+
},
1008+
[SOURCE_LABWARE]: {
1009+
...robotState.labware[SOURCE_LABWARE],
1010+
stack: [SOURCE_LABWARE, 'A2'],
1011+
},
1012+
},
1013+
}
1014+
1015+
const params = {
1016+
labwareId: SOURCE_LABWARE,
1017+
newLocation: { labwareId: 'stackingLabware' },
1018+
strategy: 'usingGripper',
1019+
} as MoveLabwareParams
1020+
1021+
const result = moveLabware(params, invariantContext, robotState)
1022+
expect(getSuccessResult(result).python).toBe(
1023+
`protocol.move_lid("A2", stacking_labware, use_gripper=True)`
1024+
)
1025+
})
1026+
1027+
it('should return an error when moving a lid from a stack to an incompatible labware', () => {
1028+
invariantContext = {
1029+
...invariantContext,
1030+
labwareEntities: {
1031+
...invariantContext.labwareEntities,
1032+
[SOURCE_LABWARE]: {
1033+
...invariantContext.labwareEntities[SOURCE_LABWARE],
1034+
def: {
1035+
...invariantContext.labwareEntities[SOURCE_LABWARE].def,
1036+
allowedRoles: ['lid'],
1037+
} as LabwareDefinition2,
1038+
},
1039+
stackingLabware: {
1040+
def: {
1041+
...fixture12Trough,
1042+
} as LabwareDefinition2,
1043+
pythonName: 'stacking_labware',
1044+
} as any,
1045+
},
1046+
} as InvariantContext
1047+
1048+
robotState = {
1049+
...robotState,
1050+
labware: {
1051+
...robotState.labware,
1052+
stackingLabware: {
1053+
stack: ['stackingLabware', 'A1'],
1054+
},
1055+
[SOURCE_LABWARE]: {
1056+
...robotState.labware[SOURCE_LABWARE],
1057+
stack: [SOURCE_LABWARE, 'A2'],
1058+
},
1059+
},
1060+
}
1061+
1062+
const params = {
1063+
labwareId: SOURCE_LABWARE,
1064+
newLocation: { labwareId: 'stackingLabware' },
1065+
strategy: 'usingGripper',
1066+
} as MoveLabwareParams
1067+
1068+
const result = moveLabware(params, invariantContext, robotState)
1069+
const { errors } = getErrorResult(result)
1070+
expect(errors).toHaveLength(1)
1071+
expect(errors[0]).toMatchObject({
1072+
type: 'LABWARE_ON_ANOTHER_ENTITY',
1073+
})
1074+
})
7811075
})

0 commit comments

Comments
 (0)