changeset 0:ed297a952c06 draft

"planemo upload commit 84e1d541403efed25838475a7bb0e08d9c7d3cf3-dirty"
author tduigou
date Tue, 14 Dec 2021 14:46:38 +0000
parents
children c3db8a581ca5
files test-data/constructs.csv test-data/dnabot_scripts.tar test-data/dnabot_scripts/output/1_clip_ot2_APIv1.py test-data/dnabot_scripts/output/1_clip_ot2_APIv2.8.py test-data/dnabot_scripts/output/1_clip_ot2_Thermocycler_APIv2.8.py test-data/dnabot_scripts/output/2_purification_ot2_APIv1.py test-data/dnabot_scripts/output/2_purification_ot2_APIv2.8.py test-data/dnabot_scripts/output/3_assembly_ot2_APIv1.py test-data/dnabot_scripts/output/3_assembly_ot2_APIv2.8.py test-data/dnabot_scripts/output/3_assembly_ot2_Thermocycler_APIv2.8.py test-data/dnabot_scripts/output/4_transformation_ot2_APIv1.py test-data/dnabot_scripts/output/4_transformation_ot2_APIv2.8.py test-data/dnabot_scripts/output/4_transformation_ot2_Thermocycler_APIv2.8.py test-data/dnabot_scripts/output/metainformation/dataset_4472_clip_run_info.csv test-data/dnabot_scripts/output/metainformation/dataset_4472_final_assembly_run_info.csv test-data/dnabot_scripts/output/metainformation/dataset_4472_wells.txt test-data/linker_parts_coords.csv test-data/user_parts_coords.csv wrap.xml
diffstat 19 files changed, 2604 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/constructs.csv	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,4 @@
+Well,Linker 1,Part 1,Linker 2,Part 2,Linker 3,Part 3,Linker 4,Part 4,Linker 5,Part 5,Linker 6,Part 6,Linker 7,Part 7,Linker 8,Part 8,Linker 9,Part 9,Linker 10,Part 10
+A1,LMS,BASIC_SEVA_37_CmR-p15A.1,LMP,PJ23104_BASIC,U1-RBS2,D5AP78,U3-RBS3,Q1XBU4,U2-RBS1,O66129,,,,,,,,,,
+A2,LMS,BASIC_SEVA_37_CmR-p15A.1,LMP,PJ23101_BASIC,U1-RBS1,D2WKD9,U2-RBS3,O48935,U3-RBS1,O66952,,,,,,,,,,
+A3,LMS,BASIC_SEVA_37_CmR-p15A.1,LMP,PJ23104_BASIC,U2-RBS2,P48537,U3-RBS2,O07333,U1-RBS2,Q9C446,,,,,,,,,,
Binary file test-data/dnabot_scripts.tar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/1_clip_ot2_APIv1.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,90 @@
+from opentrons import labware, instruments, modules, robot
+
+
+clips_dict={"prefixes_wells": ["B8", "B7", "C5", "C12", "C7", "B7", "C4", "C9", "C10", "B7", "C8", "C11", "C5"], "prefixes_plates": ["2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2"], "suffixes_wells": ["A7", "C1", "C3", "C2", "A8", "C1", "C2", "C3", "A8", "C2", "C3", "C1", "A8"], "suffixes_plates": ["2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2"], "parts_wells": ["A1", "A10", "A3", "A11", "A6", "A9", "A2", "A5", "A7", "A10", "A8", "A4", "A12"], "parts_plates": ["5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5"], "parts_vols": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "water_vols": [7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0]}
+
+
+def clip(
+        prefixes_wells,
+        prefixes_plates,
+        suffixes_wells,
+        suffixes_plates,
+        parts_wells,
+        parts_plates,
+        parts_vols,
+        water_vols,
+        tiprack_type="tiprack-10ul"):
+    """Implements linker ligation reactions using an opentrons OT-2."""
+
+    # Constants
+    INITIAL_TIP = 'A1'
+    CANDIDATE_TIPRACK_SLOTS = ['3', '6', '9']
+    PIPETTE_TYPE = 'P10_Single'
+    PIPETTE_MOUNT = 'right'
+    SOURCE_PLATE_TYPE = '4ti-0960_FrameStar'
+    DESTINATION_PLATE_TYPE = '4ti-0960_FrameStar'
+    DESTINATION_PLATE_POSITION = '1'
+    TUBE_RACK_TYPE = 'tube-rack_E1415-1500'
+    TUBE_RACK_POSITION = '4'
+    MASTER_MIX_WELL = 'A1'
+    WATER_WELL = 'A2'
+    INITIAL_DESTINATION_WELL = 'A1'
+    MASTER_MIX_VOLUME = 20
+    LINKER_MIX_SETTINGS = (1, 3)
+    PART_MIX_SETTINGS = (4, 5)
+
+    # Tiprack slots
+    total_tips = 4 * len(parts_wells)
+    letter_dict = {'A': 0, 'B': 1, 'C': 2,
+                   'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7}
+    tiprack_1_tips = (
+        13 - int(INITIAL_TIP[1:])) * 8 - letter_dict[INITIAL_TIP[0]]
+    if total_tips > tiprack_1_tips:
+        tiprack_num = 1 + (total_tips - tiprack_1_tips) // 96 + \
+            (1 if (total_tips - tiprack_1_tips) % 96 > 0 else 0)
+    else:
+        tiprack_num = 1
+    slots = CANDIDATE_TIPRACK_SLOTS[:tiprack_num]
+
+    # Define source plates
+    source_plates = {}
+    source_plates_keys = list(set((prefixes_plates + suffixes_plates + parts_plates)))
+    for key in source_plates_keys:
+        source_plates[key] = labware.load(SOURCE_PLATE_TYPE, key)
+
+    # Define remaining labware
+    tipracks = [labware.load(tiprack_type, slot) for slot in slots]
+    if PIPETTE_TYPE != 'P10_Single':
+        print('Define labware must be changed to use', PIPETTE_TYPE)
+        exit()
+    pipette = instruments.P10_Single(mount=PIPETTE_MOUNT, tip_racks=tipracks)
+    pipette.start_at_tip(tipracks[0].well(INITIAL_TIP))
+    destination_plate = labware.load(
+        DESTINATION_PLATE_TYPE, DESTINATION_PLATE_POSITION)
+    tube_rack = labware.load(TUBE_RACK_TYPE, TUBE_RACK_POSITION)
+    master_mix = tube_rack.wells(MASTER_MIX_WELL)
+    water = tube_rack.wells(WATER_WELL)
+    destination_wells = destination_plate.wells(
+        INITIAL_DESTINATION_WELL, length=int(len(parts_wells)))
+
+    # Transfers
+    pipette.pick_up_tip()
+    pipette.transfer(MASTER_MIX_VOLUME, master_mix,
+                     destination_wells, new_tip='never')
+    pipette.drop_tip()
+    pipette.transfer(water_vols, water,
+                     destination_wells, new_tip='always')
+    for clip_num in range(len(parts_wells)):
+        pipette.transfer(1, source_plates[prefixes_plates[clip_num]].wells(prefixes_wells[clip_num]),
+                         destination_wells[clip_num], mix_after=LINKER_MIX_SETTINGS)
+        pipette.transfer(1, source_plates[suffixes_plates[clip_num]].wells(suffixes_wells[clip_num]),
+                         destination_wells[clip_num], mix_after=LINKER_MIX_SETTINGS)
+        pipette.transfer(parts_vols[clip_num], source_plates[parts_plates[clip_num]].wells(parts_wells[clip_num]),
+                         destination_wells[clip_num], mix_after=PART_MIX_SETTINGS)
+
+
+clip(**clips_dict)
+
+# Robot comments
+for c in robot.commands():
+    print(c)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/1_clip_ot2_APIv2.8.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,137 @@
+from opentrons import protocol_api
+
+# Rename to 'clip_template' and paste into 'template_ot2_scripts' folder in DNA-BOT to use
+# Code has been reordered to better group relevant commands and take the constants out of def clip()
+
+#metadata
+metadata = {
+     'apiLevel': '2.8',
+     'protocolName': 'CLIP_No_Thermocycler',
+     'description': 'Implements linker ligation reactions using an opentrons OT-2. This version does not include the Thermocycler module.'}
+
+# example dictionary produced by DNA-BOT for a single construct containing 5 parts, un-comment and run to test the template
+#clips_dict={"prefixes_wells": ["A8", "A7", "C5", "C7", "C10"], "prefixes_plates": ["2", "2", "2", "2", "2"], "suffixes_wells": ["B7", "C1", "C2", "C3", "B8"], "suffixes_plates": ["2", "2", "2", "2", "2"], "parts_wells": ["E2", "F2", "C2", "B2", "D2"], "parts_plates": ["5", "5", "5", "5", "5"], "parts_vols": [1, 1, 1, 1, 1], "water_vols": [7.0, 7.0, 7.0, 7.0, 7.0]}
+
+# __LABWARES is expected to be redefined by "generate_ot2_script" method
+# Test dict
+# __LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+
+clips_dict={"prefixes_wells": ["B8", "B7", "C5", "C12", "C7", "B7", "C4", "C9", "C10", "B7", "C8", "C11", "C5"], "prefixes_plates": ["2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2"], "suffixes_wells": ["A7", "C1", "C3", "C2", "A8", "C1", "C2", "C3", "A8", "C2", "C3", "C1", "A8"], "suffixes_plates": ["2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2"], "parts_wells": ["A1", "A10", "A3", "A11", "A6", "A9", "A2", "A5", "A7", "A10", "A8", "A4", "A12"], "parts_plates": ["5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5"], "parts_vols": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "water_vols": [7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0]}
+__LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+# added run function for API 2.8
+
+    ### Constants - these have been moved out of the def clip() for clarity
+
+    #Tiprack
+    tiprack_type=__LABWARES['96_tiprack_20ul']['id']
+    INITIAL_TIP = 'A1'
+    CANDIDATE_TIPRACK_SLOTS = ['3', '6', '9']
+
+    # Pipettes - pipette instructions in a single location so redefining pipette type is simpler
+    PIPETTE_TYPE = __LABWARES['p20_single']['id']
+             # API 2 supports gen_1 pipettes like the p10_single
+    PIPETTE_MOUNT = 'right'
+        ### Load Pipette
+        # checks if it's a P10 Single pipette
+    if PIPETTE_TYPE != 'p20_single_gen2':
+        print('Define labware must be changed to use', PIPETTE_TYPE)
+        exit()
+
+    # Source Plates
+    SOURCE_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+            # modified from custom labware as API 2 doesn't support labware.create anymore, so the old add_labware script can't be used
+
+    # Destination Plates
+    DESTINATION_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+    DESTINATION_PLATE_POSITION = '1'
+            # INITIAL_DESTINATION_WELL constant removed, as destination_plate.wells() automatically starts from A1
+
+    # Tube Rack
+    TUBE_RACK_TYPE = __LABWARES['24_tuberack_1500ul']['id']
+    TUBE_RACK_POSITION = '4'
+    MASTER_MIX_WELL = 'A1'
+    WATER_WELL = 'A2'
+    MASTER_MIX_VOLUME = 20
+
+    # Mix settings
+    LINKER_MIX_SETTINGS = (1, 3)
+    PART_MIX_SETTINGS = (4, 5)
+
+    def clip(
+            prefixes_wells,
+            prefixes_plates,
+            suffixes_wells,
+            suffixes_plates,
+            parts_wells,
+            parts_plates,
+            parts_vols,
+            water_vols):
+
+        ### Loading Tiprack
+        # Calculates whether one, two, or three tipracks are needed, which are in slots 3, 6, and 9 respectively
+        total_tips = 4 * len(parts_wells)
+        letter_dict = {'A': 0, 'B': 1, 'C': 2,
+                       'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7}
+        tiprack_1_tips = (
+            13 - int(INITIAL_TIP[1:])) * 8 - letter_dict[INITIAL_TIP[0]]
+        if total_tips > tiprack_1_tips:
+            tiprack_num = 1 + (total_tips - tiprack_1_tips) // 96 + \
+            (1 if (total_tips - tiprack_1_tips) % 96 > 0 else 0)
+        else:
+            tiprack_num = 1
+        slots = CANDIDATE_TIPRACK_SLOTS[:tiprack_num]
+
+        # loads the correct number of tipracks
+        tipracks = [protocol.load_labware(tiprack_type, slot) for slot in slots]
+
+        # Loads pipette according to constants assigned above
+        pipette = protocol.load_instrument(PIPETTE_TYPE, mount=PIPETTE_MOUNT, tip_racks=tipracks)
+
+        ### Load Destination Plate
+        # Loads destination plate according to constants assigned above
+        destination_plate = protocol.load_labware(DESTINATION_PLATE_TYPE, DESTINATION_PLATE_POSITION)
+
+        # Defines where the destination wells are within the destination plate
+        destination_wells = destination_plate.wells()[0:len(parts_wells)]
+
+        ### Load Tube Rack
+        # Loads tube rack according to constants assigned above
+        tube_rack = protocol.load_labware(TUBE_RACK_TYPE, TUBE_RACK_POSITION)
+
+        # Defines positions of master mix and water within the tube rack
+        master_mix = tube_rack.wells(MASTER_MIX_WELL)
+        water = tube_rack.wells(WATER_WELL)
+
+         ### Loading Source Plates
+        # Makes a source plate key for where prefixes, suffixes, and parts are located, according to the dictionary generated by the DNA-BOT
+        source_plates = {}
+        source_plates_keys = list(set((prefixes_plates + suffixes_plates + parts_plates)))
+
+        # Loads plates according to the source plate key
+        for key in source_plates_keys:
+            source_plates[key]=protocol.load_labware(SOURCE_PLATE_TYPE, key)
+
+        ### Transfers
+
+        # transfer master mix into destination wells
+            # added blowout into destination wells ('blowout_location' only works for API 2.8 and above)
+        pipette.pick_up_tip()
+        pipette.transfer(MASTER_MIX_VOLUME, master_mix, destination_wells, blow_out=True, blowout_location='destination well', new_tip='never')
+        pipette.drop_tip()
+
+        # transfer water into destination wells
+            # added blowout into destination wells ('blowout_location' only works for API 2.8 and above)
+        pipette.transfer(water_vols, water, destination_wells, blow_out=True, blowout_location='destination well', new_tip='always')
+
+        #transfer prefixes, suffixes, and parts into destination wells
+            # added blowout into destination wells ('blowout_location' only works for API 2.8 and above)
+        for clip_num in range(len(parts_wells)):
+            pipette.transfer(1, source_plates[prefixes_plates[clip_num]].wells(prefixes_wells[clip_num]), destination_wells[clip_num], blow_out=True, blowout_location='destination well', new_tip='always', mix_after=LINKER_MIX_SETTINGS)
+            pipette.transfer(1, source_plates[suffixes_plates[clip_num]].wells(suffixes_wells[clip_num]), destination_wells[clip_num], blow_out=True, blowout_location='destination well', new_tip='always', mix_after=LINKER_MIX_SETTINGS)
+            pipette.transfer(parts_vols[clip_num], source_plates[parts_plates[clip_num]].wells(parts_wells[clip_num]), destination_wells[clip_num], blow_out=True, blowout_location='destination well', new_tip='always', mix_after=PART_MIX_SETTINGS)
+
+    # the run function will first define the CLIP function, and then run the CLIP function with the dictionary produced by DNA-BOT
+    clip(**clips_dict)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/1_clip_ot2_Thermocycler_APIv2.8.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,152 @@
+from opentrons import protocol_api
+
+# Rename to 'clip_template' and paste into 'template_ot2_scripts' folder in DNA-BOT to use
+
+#metadata
+metadata = {
+     'apiLevel': '2.8',
+     'protocolName': 'CLIP_With_Thermocycler',
+     'description': 'Implements linker ligation reactions using an opentrons OT-2, including the thermocycler module.'}
+
+
+
+# example dictionary produced by DNA-BOT for a single construct containing 5 parts, un-comment and run to test the template
+#clips_dict={"prefixes_wells": ["A8", "A7", "C5", "C7", "C10"], "prefixes_plates": ["2", "2", "2", "2", "2"], "suffixes_wells": ["B7", "C1", "C2", "C3", "B8"], "suffixes_plates": ["2", "2", "2", "2", "2"], "parts_wells": ["E2", "F2", "C2", "B2", "D2"], "parts_plates": ["5", "5", "5", "5", "5"], "parts_vols": [1, 1, 1, 1, 1], "water_vols": [7.0, 7.0, 7.0, 7.0, 7.0]}
+
+# __LABWARES is expected to be redefined by "generate_ot2_script" method
+# Test dict
+# __LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+
+clips_dict={"prefixes_wells": ["B8", "B7", "C5", "C12", "C7", "B7", "C4", "C9", "C10", "B7", "C8", "C11", "C5"], "prefixes_plates": ["2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2"], "suffixes_wells": ["A7", "C1", "C3", "C2", "A8", "C1", "C2", "C3", "A8", "C2", "C3", "C1", "A8"], "suffixes_plates": ["2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2"], "parts_wells": ["A1", "A10", "A3", "A11", "A6", "A9", "A2", "A5", "A7", "A10", "A8", "A4", "A12"], "parts_plates": ["5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5", "5"], "parts_vols": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "water_vols": [7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0, 7.0]}
+__LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+# added run function for API 2.8
+
+    ### Constants - these have been moved out of the def clip() for clarity
+
+    #Tiprack
+    tiprack_type=__LABWARES['96_tiprack_20ul']['id']
+    INITIAL_TIP = 'A1'
+    CANDIDATE_TIPRACK_SLOTS = ['3', '6', '9']
+
+    # Pipettes - pipette instructions in a single location so redefining pipette type is simpler
+    PIPETTE_TYPE = __LABWARES['p20_single']['id']
+    PIPETTE_MOUNT = 'right'
+        ### Load Pipette
+        # checks if it's a P10 Single pipette
+    if PIPETTE_TYPE != 'p20_single_gen2':
+        print('Define labware must be changed to use', PIPETTE_TYPE)
+        exit()
+#Thermocycler Module
+    tc_mod = protocol.load_module('Thermocycler Module')
+# Destination Plates
+    DESTINATION_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+    # Loads destination plate onto Thermocycler Module
+    destination_plate = tc_mod.load_labware(DESTINATION_PLATE_TYPE)
+
+    # Source Plates
+    SOURCE_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+            # modified from custom labware as API 2 doesn't support labware.create anymore, so the old add_labware script can't be used
+
+    # Tube Rack
+    TUBE_RACK_TYPE = __LABWARES['24_tuberack_1500ul']['id']
+            # modified from custom labware as API 2 doesn't support labware.create anymore, so the old add_labware script can't be used
+    TUBE_RACK_POSITION = '4'
+    MASTER_MIX_WELL = 'A1'
+    WATER_WELL = 'A2'
+    MASTER_MIX_VOLUME = 20
+
+    # Mix settings
+    LINKER_MIX_SETTINGS = (1, 3)
+    PART_MIX_SETTINGS = (4, 5)
+
+    def clip(
+            prefixes_wells,
+            prefixes_plates,
+            suffixes_wells,
+            suffixes_plates,
+            parts_wells,
+            parts_plates,
+            parts_vols,
+            water_vols):
+
+        ### Loading Tiprack
+        # Calculates whether one, two, or three tipracks are needed, which are in slots 3, 6, and 9 respectively
+        total_tips = 4 * len(parts_wells)
+        letter_dict = {'A': 0, 'B': 1, 'C': 2,
+                       'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7}
+        tiprack_1_tips = (
+            13 - int(INITIAL_TIP[1:])) * 8 - letter_dict[INITIAL_TIP[0]]
+        if total_tips > tiprack_1_tips:
+            tiprack_num = 1 + (total_tips - tiprack_1_tips) // 96 + \
+            (1 if (total_tips - tiprack_1_tips) % 96 > 0 else 0)
+        else:
+            tiprack_num = 1
+        slots = CANDIDATE_TIPRACK_SLOTS[:tiprack_num]
+
+        # loads the correct number of tipracks
+        tipracks = [protocol.load_labware(tiprack_type, slot) for slot in slots]
+            # changed to protocol.load_labware for API 2.8
+
+        # Loads pipette according to constants assigned above
+        pipette = protocol.load_instrument(PIPETTE_TYPE, mount=PIPETTE_MOUNT, tip_racks=tipracks)
+
+        # Defines where the destination wells are within the destination plate
+        destination_wells = destination_plate.wells()[0:len(parts_wells)]
+
+        ### Load Tube Rack
+        # Loads tube rack according to constants assigned above
+        tube_rack = protocol.load_labware(TUBE_RACK_TYPE, TUBE_RACK_POSITION)
+
+        # Defines positions of master mix and water within the tube rack
+        master_mix = tube_rack.wells(MASTER_MIX_WELL)
+        water = tube_rack.wells(WATER_WELL)
+
+         ### Loading Source Plates
+        # Makes a source plate key for where prefixes, suffixes, and parts are located, according to the dictionary generated by the DNA-BOT
+        source_plates = {}
+        source_plates_keys = list(set((prefixes_plates + suffixes_plates + parts_plates)))
+
+        # Loads plates according to the source plate key
+        for key in source_plates_keys:
+            source_plates[key]=protocol.load_labware(SOURCE_PLATE_TYPE, key)
+
+        ### Transfers
+
+        # transfer master mix into destination wells
+            # added blowout into destination wells ('blowout_location' only works for API 2.8 and above)
+        pipette.pick_up_tip()
+        pipette.transfer(MASTER_MIX_VOLUME, master_mix, destination_wells, blow_out=True, blowout_location='destination well', new_tip='never')
+        pipette.drop_tip()
+
+        # transfer water into destination wells
+            # added blowout into destination wells ('blowout_location' only works for API 2.8 and above)
+        pipette.transfer(water_vols, water, destination_wells, blow_out=True, blowout_location='destination well', new_tip='always')
+
+        #transfer prefixes, suffixes, and parts into destination wells
+            # added blowout into destination wells ('blowout_location' only works for API 2.8 and above)
+        for clip_num in range(len(parts_wells)):
+            pipette.transfer(1, source_plates[prefixes_plates[clip_num]].wells(prefixes_wells[clip_num]), destination_wells[clip_num], blow_out=True, blowout_location='destination well', new_tip='always', mix_after=LINKER_MIX_SETTINGS)
+            pipette.transfer(1, source_plates[suffixes_plates[clip_num]].wells(suffixes_wells[clip_num]), destination_wells[clip_num], blow_out=True, blowout_location='destination well', new_tip='always', mix_after=LINKER_MIX_SETTINGS)
+            pipette.transfer(parts_vols[clip_num], source_plates[parts_plates[clip_num]].wells(parts_wells[clip_num]), destination_wells[clip_num], blow_out=True, blowout_location='destination well', new_tip='always', mix_after=PART_MIX_SETTINGS)
+
+    # the run function will first define the CLIP function, and then run the CLIP function with the dictionary produced by DNA-BOT
+    clip(**clips_dict)
+    ### PCR Reaction in Thermocycler
+
+    # close lid and set lid temperature, PCR will not start until lid reaches 37C
+    tc_mod.close_lid()
+    tc_mod.set_lid_temperature(105)
+
+    # Runs 20 cycles of 37C for 2 minutes and 20C for 1 minute, then holds for 60C for 10 minutes
+    profile = [
+        {'temperature': 37, 'hold_time_minutes': 2},
+        {'temperature': 20, 'hold_time_minutes': 1}]
+    tc_mod.execute_profile(steps=profile, repetitions=20, block_max_volume=30)
+    tc_mod.set_block_temperature(60, hold_time_minutes=10, block_max_volume=30)
+    tc_mod.set_block_temperature(4, hold_time_minutes=2, block_max_volume=30)
+    #Q Does block_max_volume define total volume in block or individual wells?
+    tc_mod.set_lid_temperature(37)
+    tc_mod.open_lid()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/2_purification_ot2_APIv1.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,182 @@
+from opentrons import labware, instruments, modules, robot
+
+
+sample_number=13
+ethanol_well='A11'
+
+
+def magbead(
+        sample_number,
+        ethanol_well,
+        elution_buffer_well,
+        sample_volume=30,
+        bead_ratio=1.8,
+        elution_buffer_volume=40,
+        incubation_time=5,
+        settling_time=2,
+        drying_time=5,
+        elution_time=2,
+        sample_offset=0,
+        tiprack_type="opentrons_96_tiprack_300ul"):
+    """Implements magbead purification reactions for BASIC assembly using an opentrons OT-2.
+
+    Selected args:
+        ethanol_well (str): well in reagent container containing ethanol.
+        elution_buffer_well (str): well in reagent container containing elution buffer.
+        sample_offset (int): offset the intial sample column by the specified value.
+
+    """
+
+    # Constants
+    PIPETTE_ASPIRATE_RATE = 25
+    PIPETTE_DISPENSE_RATE = 150
+    TIPS_PER_SAMPLE = 9
+    CANDIDATE_TIPRACK_SLOTS = ['3', '6', '9', '2', '5']
+    MAGDECK_POSITION = '1'
+    MIX_PLATE_TYPE = '4ti-0960_FrameStar'
+    MIX_PLATE_POSITION = '4'
+    REAGENT_CONTAINER_TYPE = '4ti0131_trough-12'
+    REAGENT_CONTAINER_POSITION = '7'
+    BEAD_CONTAINER_TYPE = '4ti0136_96_deep-well'
+    BEAD_CONTAINER_POSITION = '8'
+    LIQUID_WASTE_WELL = 'A12'
+    BEADS_WELL = 'A1'
+    DEAD_TOTAL_VOL = 5
+    SLOW_HEAD_SPEEDS = {'x': 600 // 4, 'y': 400 // 4,
+                        'z': 125 // 10, 'a': 125 // 10}
+    DEFAULT_HEAD_SPEEDS = {'x': 400, 'y': 400, 'z': 125, 'a': 100}
+    IMMOBILISE_MIX_REPS = 10
+    MAGDECK_HEIGHT = 20
+    AIR_VOL_COEFF = 0.1
+    ETHANOL_VOL = 150
+    WASH_TIME = 0.5
+    ETHANOL_DEAD_VOL = 50
+    ELUTION_MIX_REPS = 20
+    ELUTANT_SEP_TIME = 1
+    ELUTION_DEAD_VOL = 2
+
+    # Errors
+    if sample_number > 48:
+        raise ValueError('sample number cannot exceed 48')
+
+    # Tips and pipette
+    total_tips = sample_number * TIPS_PER_SAMPLE
+    tiprack_num = total_tips // 96 + (1 if total_tips % 96 > 0 else 0)
+    slots = CANDIDATE_TIPRACK_SLOTS[:tiprack_num]
+    tipracks = [labware.load(tiprack_type, slot)
+                for slot in slots]
+    pipette = instruments.P300_Multi(
+        mount="left",
+        tip_racks=tipracks,
+        aspirate_flow_rate=PIPETTE_ASPIRATE_RATE,
+        dispense_flow_rate=PIPETTE_DISPENSE_RATE)
+
+    # Define labware
+    MAGDECK = modules.load('magdeck', MAGDECK_POSITION)
+    MAGDECK.disengage()
+    mag_plate = labware.load(MIX_PLATE_TYPE, MAGDECK_POSITION, share=True)
+    mix_plate = labware.load(MIX_PLATE_TYPE, MIX_PLATE_POSITION)
+    reagent_container = labware.load(
+        REAGENT_CONTAINER_TYPE, REAGENT_CONTAINER_POSITION)
+    bead_container = labware.load(BEAD_CONTAINER_TYPE, BEAD_CONTAINER_POSITION)
+    col_num = sample_number // 8 + (1 if sample_number % 8 > 0 else 0)
+    samples = [col for col in mag_plate.cols(
+    )[0 + sample_offset:col_num + sample_offset]]
+    output = [col for col in mag_plate.cols(
+    )[6 + sample_offset:col_num + 6 + sample_offset]]
+    mixing = [col for col in mix_plate.cols(
+    )[0 + sample_offset:col_num + sample_offset]]
+
+    # Define reagents and liquid waste
+    liquid_waste = reagent_container.wells(LIQUID_WASTE_WELL)
+    beads = bead_container.wells(BEADS_WELL)
+    ethanol = reagent_container.wells(ethanol_well)
+    elution_buffer = reagent_container.wells(elution_buffer_well)
+
+    # Define bead and mix volume
+    bead_volume = sample_volume * bead_ratio
+    if bead_volume / 2 > pipette.max_volume:
+        mix_vol = pipette.max_volume
+    else:
+        mix_vol = bead_volume / 2
+    total_vol = bead_volume + sample_volume + DEAD_TOTAL_VOL
+
+    # Mix beads and PCR samples and incubate
+    for target in range(int(len(samples))):
+        # Aspirate beads
+        pipette.pick_up_tip()
+        pipette.aspirate(bead_volume, beads)
+        robot.head_speed(**SLOW_HEAD_SPEEDS, combined_speed=max(SLOW_HEAD_SPEEDS.values()))
+
+        # Transfer and mix on  mix_plate
+        pipette.aspirate(sample_volume + DEAD_TOTAL_VOL, samples[target])
+        pipette.dispense(total_vol, mixing[target])
+        pipette.mix(IMMOBILISE_MIX_REPS, mix_vol, mixing[target])
+        pipette.blow_out()
+
+        # Dispose of tip
+        robot.head_speed(**DEFAULT_HEAD_SPEEDS, combined_speed=max(DEFAULT_HEAD_SPEEDS.values()))
+        pipette.drop_tip()
+
+    # Immobilise sample
+    pipette.delay(minutes=incubation_time)
+
+    # Transfer sample back to magdeck
+    for target in range(int(len(samples))):
+        pipette.transfer(total_vol, mixing[target], samples[target],
+                         blow_out=True)
+
+    # Engagae MagDeck and incubate
+    MAGDECK.engage(height=MAGDECK_HEIGHT)
+    pipette.delay(minutes=settling_time)
+
+    # Remove supernatant from magnetic beads
+    for target in samples:
+        pipette.transfer(total_vol, target, liquid_waste, blow_out=True)
+
+    # Wash beads twice with 70% ethanol
+    air_vol = pipette.max_volume * AIR_VOL_COEFF
+    for cycle in range(2):
+        for target in samples:
+            pipette.transfer(ETHANOL_VOL, ethanol, target, air_gap=air_vol)
+        pipette.delay(minutes=WASH_TIME)
+        for target in samples:
+            pipette.transfer(ETHANOL_VOL + ETHANOL_DEAD_VOL, target, liquid_waste,
+                             air_gap=air_vol)
+
+    # Dry at RT
+    pipette.delay(minutes=drying_time)
+
+    # Disengage MagDeck
+    MAGDECK.disengage()
+
+    # Mix beads with elution buffer
+    if elution_buffer_volume / 2 > pipette.max_volume:
+        mix_vol = pipette.max_volume
+    else:
+        mix_vol = elution_buffer_volume / 2
+    for target in samples:
+        pipette.transfer(elution_buffer_volume, elution_buffer,
+                         target, mix_after=(ELUTION_MIX_REPS, mix_vol))
+
+    # Incubate at RT for "elution_time" minutes
+    pipette.delay(minutes=elution_time)
+
+    # Engagae MagDeck for 1 minute and remain engaged for DNA elution
+    MAGDECK.engage(height=MAGDECK_HEIGHT)
+    pipette.delay(minutes=ELUTANT_SEP_TIME)
+
+    # Transfer clean PCR product to a new well
+    for target, dest in zip(samples, output):
+        pipette.transfer(elution_buffer_volume - ELUTION_DEAD_VOL, target,
+                         dest, blow_out=False)
+
+    # Disengage MagDeck
+    MAGDECK.disengage()
+
+
+magbead(sample_number=sample_number,
+        ethanol_well=ethanol_well, elution_buffer_well='A1')
+
+for c in robot.commands():
+    print(c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/2_purification_ot2_APIv2.8.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,261 @@
+from opentrons import protocol_api
+
+# Rename to 'purification_template' and paste into 'template_ot2_scripts' folder in DNA-BOT to use
+
+metadata = {
+     'apiLevel': '2.8',
+     'protocolName': 'purification_template',
+     'description': 'Implements magbead purification reactions for BASIC assembly using an opentrons OT-2'}
+
+
+
+
+# example values produced by DNA-BOT for a single construct containing 5 parts, un-comment and run to test the template:
+#sample_number=8
+#ethanol_well='A3'
+
+# __LABWARES and __PARAMETERS are expected to be redefined by "generate_ot2_script" method
+# Test dict
+# __LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+# __PARAMETERS={"purif_magdeck_height": {"value": 20.0}, "purif_wash_time": {"value": 0.5}, "purif_bead_ratio": {"value": 1.8}, "purif_incubation_time": {"value": 5.0}, "purif_settling_time": {"value": 2.0}, "purif_drying_time": {"value": 5.0}, "purif_elution_time": {"value": 2.0}, "transfo_incubation_temp": {"value": 4.0}, "transfo_incubation_time": {"value": 20.0}}
+
+sample_number=13
+ethanol_well='A11'
+__LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+__PARAMETERS={"purif_magdeck_height": {"value": 20}, "purif_wash_time": {"value": 0}, "purif_bead_ratio": {"value": 1}, "purif_incubation_time": {"value": 5}, "purif_settling_time": {"value": 2}, "purif_drying_time": {"value": 5}, "purif_elution_time": {"value": 2}, "transfo_incubation_temp": {"value": 4}, "transfo_incubation_time": {"value": 20}}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+# added run function for API verison 2
+
+    def magbead(
+            sample_number,
+            ethanol_well,
+            elution_buffer_well='A1',
+            sample_volume=30,
+            bead_ratio=__PARAMETERS['purif_bead_ratio']['value'],
+            elution_buffer_volume=40,
+            incubation_time=__PARAMETERS['purif_incubation_time']['value'],
+            settling_time=__PARAMETERS['purif_settling_time']['value'],
+                # if using Gen 2 magentic module, need to change time! see: https://docs.opentrons.com/v2/new_modules.html
+                # "The GEN2 Magnetic Module uses smaller magnets than the GEN1 version...this means it will take longer for the GEN2 module to attract beads."
+                # Recommended Magnetic Module GEN2 bead attraction time:
+                    # Total liquid volume <= 50 uL: 5 minutes
+                # this template was written with the Gen 1 magnetic module, as it is compatible with API version 2
+            drying_time=__PARAMETERS['purif_drying_time']['value'],
+            elution_time=__PARAMETERS['purif_elution_time']['value'],
+            sample_offset=0,
+            tiprack_type=__LABWARES['96_tiprack_300ul']['id']):
+
+        """
+
+        Selected args:
+            ethanol_well (str): well in reagent container containing ethanol.
+            elution_buffer_well (str): well in reagent container containing elution buffer.
+            sample_offset (int): offset the intial sample column by the specified value.
+
+        """
+
+
+        ### Constants
+
+        # Pipettes
+        PIPETTE_ASPIRATE_RATE = 25
+        PIPETTE_DISPENSE_RATE = 150
+        TIPS_PER_SAMPLE = 9
+        PIPETTE_TYPE = __LABWARES['p300_multi']['id']
+            # new constant for easier swapping between pipette types
+
+        # Tiprack
+        CANDIDATE_TIPRACK_SLOTS = ['3', '6', '9', '2', '5']
+
+        # Magnetic Module
+        MAGDECK_POSITION = '1'
+
+        # Mix Plate
+        MIX_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_23']['id']
+            # modified from custom labware as API 2 doesn't support labware.create anymore, so the old add_labware script can't be used
+            # also acts as the type of plate loaded onto the magnetic module
+        MIX_PLATE_POSITION = '4'
+
+        # Reagents
+        REAGENT_CONTAINER_TYPE = __LABWARES['12_reservoir_21000ul']['id']
+        REAGENT_CONTAINER_POSITION = '7'
+
+        # Beads
+        BEAD_CONTAINER_TYPE = __LABWARES['96_deepwellplate_2ml']['id']
+        BEAD_CONTAINER_POSITION = '8'
+
+        # Settings
+        LIQUID_WASTE_WELL = 'A5'
+        BEADS_WELL = 'A1'
+        DEAD_TOTAL_VOL = 5
+        SLOW_HEAD_SPEEDS = {'x': 600 // 4, 'y': 400 // 4, 'z': 125 // 10, 'a': 125 // 10}
+        DEFAULT_HEAD_SPEEDS = {'x': 400, 'y': 400, 'z': 125, 'a': 100}
+        IMMOBILISE_MIX_REPS = 10
+        MAGDECK_HEIGHT = __PARAMETERS['purif_magdeck_height']['value']
+        AIR_VOL_COEFF = 0.1
+        ETHANOL_VOL = 150
+        WASH_TIME = __PARAMETERS['purif_wash_time']['value']
+        ETHANOL_DEAD_VOL = 50
+        ELUTION_MIX_REPS = 20
+        ELUTANT_SEP_TIME = 1
+        ELUTION_DEAD_VOL = 2
+
+
+        ### Errors
+        if sample_number > 48:
+            raise ValueError('sample number cannot exceed 48')
+
+
+        ### Loading Tiprack
+
+        # Calculates whether one/two/three/four/five tipracks are needed, which are in slots 3, 6, 9, 2, and 5 respectively
+        total_tips = sample_number * TIPS_PER_SAMPLE
+        tiprack_num = total_tips // 96 + (1 if total_tips % 96 > 0 else 0)
+        slots = CANDIDATE_TIPRACK_SLOTS[:tiprack_num]
+        tipracks = [protocol.load_labware(tiprack_type, slot) for slot in slots]
+            # changed to protocol.load_labware for API version 2
+
+
+        ### Loading Pipettes
+
+        pipette = protocol.load_instrument(PIPETTE_TYPE, mount="left", tip_racks=tipracks)
+        pipette.aspirate_flow_rate=PIPETTE_ASPIRATE_RATE
+        pipette.dispense_flow_rate=PIPETTE_DISPENSE_RATE
+            # for reference: default aspirate/dispense flow rate for p300_multi_gen2 is 94 ul/s
+
+        ### Define Labware
+
+        # Magnetic Module
+        MAGDECK = protocol.load_module(__LABWARES['mag_deck']['id'], MAGDECK_POSITION)
+            # 'magdeck' is the gen 1 magnetic module, use 'magnetic module gen2' for the gen 2 magentic module
+                # if using gen 2 module, need to change settling time! (see comments under Constants)
+        MAGDECK.disengage()
+            # disengages the magnets when it is turned on
+        mag_plate = MAGDECK.load_labware(MIX_PLATE_TYPE)
+
+        # Mix Plate
+        mix_plate = protocol.load_labware(MIX_PLATE_TYPE, MIX_PLATE_POSITION)
+
+        # Reagents
+        reagent_container = protocol.load_labware(REAGENT_CONTAINER_TYPE, REAGENT_CONTAINER_POSITION)
+
+        # Beads Container
+        bead_container = protocol.load_labware(BEAD_CONTAINER_TYPE, BEAD_CONTAINER_POSITION)
+
+
+        ### Calculating Columns
+
+        # Total number of columns
+        col_num = sample_number // 8 + (1 if sample_number % 8 > 0 else 0)
+
+        # Columns containing samples in location 1 (magentic module)
+            # generates a list of lists: [[A1, B1, C1...], [A2, B2, C2...]...]
+        samples = [col for col in mag_plate.columns()[sample_offset : col_num + sample_offset]]
+
+        # Columns to mix beads and samples in location 4 (mix plate)
+        mixing = [col for col in mix_plate.columns()[sample_offset:col_num + sample_offset]]
+
+        # Columns to dispense output in location 1 (magnetic module)
+            # purified parts are dispensed 6 rows to the right of their initial location
+            # this is why the number of samples cannot exceed 48
+
+        output = [col for col in mag_plate.columns()[6 + sample_offset:col_num + 6 + sample_offset]]
+
+        ### Defining Wells for Reagents, Liquid Waste, and Beads
+
+        liquid_waste = reagent_container.wells(LIQUID_WASTE_WELL)
+        ethanol = reagent_container.wells(ethanol_well)
+        elution_buffer = reagent_container.wells(elution_buffer_well)
+        beads = bead_container[BEADS_WELL]
+
+        ### Define bead and mix volume
+        bead_volume = sample_volume * bead_ratio
+        if bead_volume / 2 > pipette.max_volume:
+            mix_vol = pipette.max_volume
+        else:
+            mix_vol = bead_volume / 2
+        total_vol = bead_volume + sample_volume + DEAD_TOTAL_VOL
+
+
+        ### Steps
+
+        # Mix beads and parts
+        for target in range(int(len(samples))):
+
+            # Aspirate beads
+            pipette.pick_up_tip()
+            pipette.aspirate(bead_volume, beads)
+            protocol.max_speeds.update(SLOW_HEAD_SPEEDS)
+
+            # Aspirte samples
+            pipette.aspirate(sample_volume + DEAD_TOTAL_VOL, samples[target][0])
+
+            # Transfer and mix on mix_plate
+            pipette.dispense(total_vol, mixing[target][0])
+                # similar to above, added [0] because samples[target] returned a list of every well in column 1 rather than just one well
+            pipette.mix(IMMOBILISE_MIX_REPS, mix_vol, mixing[target][0])
+                # similar to above, added [0] because samples[target] returned a list of every well in column 1 rather than just one well
+            pipette.blow_out()
+
+            # Dispose of tip
+            protocol.max_speeds.update(DEFAULT_HEAD_SPEEDS)
+            pipette.drop_tip()
+
+        # Immobilise sample
+        protocol.delay(minutes=incubation_time)
+
+        # Transfer beads+samples back to magdeck
+        for target in range(int(len(samples))):
+            pipette.transfer(total_vol, mixing[target], samples[target], blow_out=True, blowout_location='destination well')
+            # added blowout_location=destination well because default location of blowout is waste in API version 2
+
+        # Engagae MagDeck and incubate
+        MAGDECK.engage(height=MAGDECK_HEIGHT)
+        protocol.delay(minutes=settling_time)
+
+        # Remove supernatant from magnetic beads
+        for target in samples:
+            pipette.transfer(total_vol, target, liquid_waste, blow_out=True)
+
+        # Wash beads twice with 70% ethanol
+        air_vol = pipette.max_volume * AIR_VOL_COEFF
+        for cycle in range(2):
+            for target in samples:
+                pipette.transfer(ETHANOL_VOL, ethanol, target, air_gap=air_vol)
+            protocol.delay(minutes=WASH_TIME)
+            for target in samples:
+                pipette.transfer(ETHANOL_VOL + ETHANOL_DEAD_VOL, target, liquid_waste, air_gap=air_vol)
+
+        # Dry at room temperature
+        protocol.delay(minutes=drying_time)
+
+        # Disengage MagDeck
+        MAGDECK.disengage()
+
+        # Mix beads with elution buffer
+        if elution_buffer_volume / 2 > pipette.max_volume:
+            mix_vol = pipette.max_volume
+        else:
+            mix_vol = elution_buffer_volume / 2
+        for target in samples:
+            pipette.transfer(elution_buffer_volume, elution_buffer, target, mix_after=(ELUTION_MIX_REPS, mix_vol))
+
+        # Incubate at room temperature
+        protocol.delay(minutes=elution_time)
+
+        # Engage MagDeck (remains engaged for DNA elution)
+        MAGDECK.engage(height=MAGDECK_HEIGHT)
+        protocol.delay(minutes=ELUTANT_SEP_TIME)
+
+        # Transfer purified parts to a new well
+        for target, dest in zip(samples, output):
+            pipette.transfer(elution_buffer_volume - ELUTION_DEAD_VOL, target,
+                             dest, blow_out=False)
+
+        # Disengage MagDeck
+        MAGDECK.disengage()
+
+    magbead(sample_number=sample_number, ethanol_well=ethanol_well)
+    # removed elution buffer well='A1', added that to where the function is defined
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/3_assembly_ot2_APIv1.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,84 @@
+from opentrons import labware, instruments, modules, robot
+import numpy as np
+
+
+final_assembly_dict={"A1": ["A7", "B7", "C7", "D7", "E7"], "B1": ["A7", "F7", "G7", "H7", "A8"], "C1": ["A7", "B8", "C8", "D8", "E8"]}
+tiprack_num=1
+
+
+def final_assembly(final_assembly_dict, tiprack_num, tiprack_type="tiprack-10ul"):
+    """Implements final assembly reactions using an opentrons OT-2.
+
+    Args:
+    final_assembly_dict (dict): Dictionary with keys and values corresponding to destination and associated linker-ligated part wells, respectively.
+    tiprack_num (int): Number of tipracks required during run.
+
+    """
+
+    # Constants
+    CANDIDATE_TIPRACK_SLOTS = ['3', '6', '9', '2', '5', '8', '11']
+    PIPETTE_MOUNT = 'right'
+    MAG_PLATE_TYPE = '4ti-0960_FrameStar'
+    MAG_PLATE_POSITION = '1'
+    TUBE_RACK_TYPE = 'tube-rack_E1415-1500'
+    TUBE_RACK_POSITION = '7'
+    DESTINATION_PLATE_TYPE = 'aluminium-block_4ti-0960_FrameStar'
+    TEMPDECK_SLOT = '4'
+    TEMP = 20
+    TOTAL_VOL = 15
+    PART_VOL = 1.5
+    MIX_SETTINGS = (1, 3)
+
+    # Errors
+    sample_number = len(final_assembly_dict.keys())
+    if sample_number > 96:
+        raise ValueError('Final assembly nummber cannot exceed 96.')
+
+    # Tips and pipette
+    slots = CANDIDATE_TIPRACK_SLOTS[:tiprack_num]
+    tipracks = [labware.load(tiprack_type, slot)
+                for slot in slots]
+    pipette = instruments.P10_Single(mount=PIPETTE_MOUNT, tip_racks=tipracks)
+
+    # Define Labware and set temperature
+    magbead_plate = labware.load(MAG_PLATE_TYPE, MAG_PLATE_POSITION)
+    tube_rack = labware.load(TUBE_RACK_TYPE, TUBE_RACK_POSITION)
+    tempdeck = modules.load('tempdeck', TEMPDECK_SLOT)
+    destination_plate = labware.load(
+        DESTINATION_PLATE_TYPE, TEMPDECK_SLOT, share=True)
+    tempdeck.set_temperature(TEMP)
+    tempdeck.wait_for_temp()
+
+    # Master mix transfers
+    final_assembly_lens = []
+    for values in final_assembly_dict.values():
+        final_assembly_lens.append(len(values))
+    unique_assemblies_lens = list(set(final_assembly_lens))
+    master_mix_well_letters = ['A', 'B', 'C', 'D']
+    for x in unique_assemblies_lens:
+        master_mix_well = master_mix_well_letters[(x - 1) // 6] + str(x - 1)
+        destination_inds = [i for i, lens in enumerate(
+            final_assembly_lens) if lens == x]
+        destination_wells = np.array(
+            [key for key, value in list(final_assembly_dict.items())])
+        destination_wells = list(destination_wells[destination_inds])
+        pipette.pick_up_tip()
+        pipette.transfer(TOTAL_VOL - x * PART_VOL, tube_rack.wells(master_mix_well),
+                         destination_plate.wells(destination_wells),
+                         new_tip='never')
+        pipette.drop_tip()
+
+    # Part transfers
+    for key, values in list(final_assembly_dict.items()):
+        pipette.transfer(PART_VOL, magbead_plate.wells(values),
+                         destination_plate.wells(key), mix_after=MIX_SETTINGS,
+                         new_tip='always')
+
+    tempdeck.deactivate()
+
+
+final_assembly(final_assembly_dict=final_assembly_dict,
+               tiprack_num=tiprack_num)
+
+for c in robot.commands():
+    print(c)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/3_assembly_ot2_APIv2.8.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,92 @@
+from opentrons import protocol_api
+import numpy as np
+# metadata
+metadata = {
+'protocolName': 'My Protocol',
+'description': 'Simple protocol to get started using OT2',
+'apiLevel': '2.8'
+}
+
+# protocol run function. the part after the colon lets your editor know
+
+
+# test dict can be used for simulation
+#final_assembly_dict={ "A1": ['A7', 'B7', 'C7', 'F7'], "B1": ['A7', 'B7', 'D7', 'G7'], "C1": ['A7', 'B7', 'E7', 'H7']}
+#tiprack_num=1
+
+# __LABWARES is expected to be redefined by "generate_ot2_script" method
+# Test dict
+# __LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+
+final_assembly_dict={"A1": ["A7", "B7", "C7", "D7", "E7"], "B1": ["A7", "F7", "G7", "H7", "A8"], "C1": ["A7", "B8", "C8", "D8", "E8"]}
+tiprack_num=1
+__LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+    def final_assembly(final_assembly_dict, tiprack_num, tiprack_type=__LABWARES['96_tiprack_20ul']['id']):
+            # Constants, we update all the labware name in version 2
+            #Tiprack
+            CANDIDATE_TIPRACK_SLOTS = ['3', '6', '9', '2', '5', '8', '11']
+            PIPETTE_MOUNT = 'right'
+            #Plate of sample after  purification
+            MAG_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_23']['id']
+            MAG_PLATE_POSITION = '1'
+            #Tuberack
+            TUBE_RACK_TYPE = __LABWARES['24_tuberack_1500ul']['id']
+            TUBE_RACK_POSITION = '7'
+            #Destination plate
+            DESTINATION_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_23']['id']
+            #Temperature control plate
+            TEMPDECK_SLOT = '4'
+            TEMP = 20
+            TOTAL_VOL = 15
+            PART_VOL = 1.5
+            MIX_SETTINGS = (1, 3)
+            tiprack_num=tiprack_num+1
+            # Errors
+            sample_number = len(final_assembly_dict.keys())
+            if sample_number > 96:
+                raise ValueError('Final assembly nummber cannot exceed 96.')
+
+            slots = CANDIDATE_TIPRACK_SLOTS[:tiprack_num]
+            tipracks = [protocol.load_labware(tiprack_type, slot) for slot in slots]
+            pipette = protocol.load_instrument(__LABWARES['p20_single']['id'], PIPETTE_MOUNT, tip_racks=tipracks)
+
+
+            # Define Labware and set temperature
+            magbead_plate = protocol.load_labware(MAG_PLATE_TYPE, MAG_PLATE_POSITION)
+            tube_rack = protocol.load_labware(TUBE_RACK_TYPE, TUBE_RACK_POSITION)
+            tempdeck = protocol.load_module('tempdeck', TEMPDECK_SLOT)
+            destination_plate = tempdeck.load_labware(
+            DESTINATION_PLATE_TYPE, TEMPDECK_SLOT)
+            tempdeck.set_temperature(TEMP)
+
+             # Master mix transfers
+            final_assembly_lens = []
+            for values in final_assembly_dict.values():
+                final_assembly_lens.append(len(values))
+            unique_assemblies_lens = list(set(final_assembly_lens))
+            master_mix_well_letters = ['A', 'B', 'C', 'D']
+            for x in unique_assemblies_lens:
+                master_mix_well = master_mix_well_letters[(x - 1) // 6] + str(x - 1)
+                destination_inds = [i for i, lens in enumerate(final_assembly_lens) if lens == x]
+                destination_wells = np.array([key for key, value in list(final_assembly_dict.items())])
+                destination_wells = list(destination_wells[destination_inds])
+                for destination_well in destination_wells:# make tube_rack_wells and destination_plate.wells in the same type
+                    pipette.pick_up_tip()
+                    pipette.transfer(TOTAL_VOL - x * PART_VOL, tube_rack.wells(master_mix_well),
+                                     destination_plate.wells(destination_well), new_tip='never')#transfer water and buffer in the pipette
+
+                    pipette.drop_tip()
+
+            # Part transfers
+            for key, values in list(final_assembly_dict.items()):
+                for value in values:# magbead_plate.wells and destination_plate.wells in the same type
+                    pipette.transfer(PART_VOL, magbead_plate.wells(value),
+                                     destination_plate.wells(key), mix_after=MIX_SETTINGS,
+                                     new_tip='always')#transfer parts in one tube
+
+            tempdeck.deactivate() #stop increasing the temperature
+
+    final_assembly(final_assembly_dict=final_assembly_dict, tiprack_num=tiprack_num)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/3_assembly_ot2_Thermocycler_APIv2.8.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,107 @@
+from opentrons import protocol_api
+import numpy as np
+# metadata
+metadata = {
+'protocolName': 'DNABOT Assembly Thermocycler',
+'description': 'DNABOT Assembly Step3 with Thermocycler',
+'apiLevel': '2.8'
+}
+
+# It is possible to run 88 assemblies with this new module. The heat block module is removed. 
+# Assembly reactions is set up on thermocycler module.
+
+
+# test dictionary can be used for simulation 3 or 88 assemblies
+#final_assembly_dict={"A1": ['A7', 'B7', 'C7', 'F7'], "B1": ['A7', 'B7', 'D7', 'G7'], "C1": ['A7', 'B7', 'E7', 'H7']}
+#tiprack_num=1
+
+#final_assembly_dict={"A1": ["A7", "G7", "H7", "A8", "B8"], "B1": ["A7", "D8", "E8", "F8", "G8"], "C1": ["A7", "D8", "H7", "H8", "B9"], "D1": ["A7", "C9", "E9", "G9", "B8"], "E1": ["A7", "H9", "B10", "E9", "D10"], "F1": ["A7", "C9", "H8", "F10", "D10"], "G1": ["A7", "C9", "H10", "E8", "B9"], "H1": ["A7", "H9", "F8", "H10", "B11"], "A2": ["A7", "G7", "E8", "B10", "G8"], "B2": ["A7", "G7", "D11", "A8", "B9"], "C2": ["A7", "C9", "E9", "G9", "B9"], "D2": ["A7", "G7", "H7", "H8", "B8"], "E2": ["A7", "F11", "H11", "H7", "B12"], "F2": ["A7", "C9", "H8", "H11", "D10"], "G2": ["A7", "G7", "D11", "A8", "B8"], "H2": ["B7", "F11", "B10", "H10", "B11"], "A3": ["B7", "D8", "H7", "H8", "B8"], "B3": ["B7", "C9", "H10", "G9", "B8"], "C3": ["B7", "D12", "H8", "H11", "B11"], "D3": ["B7", "D12", "E9", "E8", "B8"], "E3": ["B7", "D12", "E9", "E8", "B9"], "F3": ["B7", "H9", "B10", "H10", "D10"], "G3": ["B7", "G7", "D11", "H8", "B8"], "H3": ["B7", "D12", "H10", "G9", "B9"], "A4": ["B7", "F11", "F10", "D11", "B12"], "B4": ["B7", "G7", "H7", "A8", "B9"], "C4": ["B7", "G7", "E8", "B10", "B12"], "D4": ["B7", "H9", "H11", "H7", "G8"], "E4": ["B7", "D8", "E8", "F8", "B12"], "F4": ["B7", "D12", "E9", "G9", "B8"], "G4": ["C7", "H9", "B10", "E9", "B11"], "H4": ["C7", "F11", "B10", "H10", "D10"], "A5": ["C7", "H9", "F8", "E9", "B11"], "B5": ["C7", "D12", "H8", "F10", "B11"], "C5": ["C7", "F11", "F8", "H10", "B11"], "D5": ["C7", "F11", "H11", "H7", "G8"], "E5": ["C7", "D8", "D11", "A8", "B9"], "F5": ["C7", "H9", "H11", "H7", "B12"], "G5": ["C7", "C9", "H10", "G9", "B9"], "H5": ["C7", "H9", "F10", "H7", "G8"], "A6": ["C7", "D12", "A8", "H11", "D10"], "B6": ["C7", "C9", "A8", "H11", "B11"], "C6": ["C7", "F11", "H11", "D11", "B12"], "D6": ["C7", "D8", "E8", "B10", "G8"], "E6": ["C7", "C9", "H8", "H11", "B11"], "F6": ["D7", "D8", "G9", "F8", "G8"], "G6": ["D7", "C9", "A8", "F10", "B11"], "H6": ["D7", "F11", "F10", "H7", "B12"], "A7": ["D7", "C9", "A8", "F10", "D10"], "B7": ["D7", "H9", "F8", "E9", "D10"], "C7": ["D7", "G7", "G9", "F8", "B12"], "D7": ["D7", "D12", "A8", "H11", "B11"], "E7": ["D7", "D12", "H10", "G9", "B8"], "F7": ["D7", "H9", "H11", "D11", "B12"], "G7": ["D7", "C9", "H8", "F10", "B11"], "H7": ["D7", "D8", "D11", "H8", "B8"], "A8": ["D7", "C9", "E9", "E8", "B9"], "B8": ["D7", "H9", "F10", "D11", "G8"], "C8": ["D7", "H9", "H11", "D11", "G8"], "D8": ["D7", "D12", "A8", "F10", "D10"], "E8": ["E7", "G7", "G9", "F8", "G8"], "F8": ["E7", "D12", "A8", "F10", "B11"], "G8": ["E7", "H9", "F10", "D11", "B12"], "H8": ["E7", "D8", "E8", "B10", "B12"], "A9": ["E7", "C9", "E9", "E8", "B8"], "B9": ["E7", "F11", "B10", "E9", "D10"], "C9": ["E7", "D12", "H8", "F10", "D10"], "D9": ["E7", "H9", "B10", "H10", "B11"], "E9": ["E7", "D8", "G9", "F8", "B12"], "F9": ["E7", "F11", "B10", "E9", "B11"], "G9": ["E7", "F11", "F8", "E9", "C11"], "H9": ["E7", "G7", "G9", "B10", "B12"], "A10": ["E7", "D8", "G9", "B10", "B12"], "B10": ["E7", "D8", "D11", "A8", "B8"], "C10": ["E7", "F11", "F10", "H7", "G8"], "D10": ["F7", "F11", "F8", "E9", "D10"], "E10": ["F7", "H9", "F10", "H7", "B12"], "F10": ["F7", "D12", "H10", "E8", "B9"], "G10": ["F7", "C9", "H10", "E8", "B8"], "H10": ["F7", "F11", "F8", "H10", "D10"], "A11": ["F7", "D12", "H10", "E8", "B8"], "B11": ["F7", "G7", "H7", "H8", "B9"], "C11": ["F7", "G7", "G9", "B10", "G8"], "D11": ["F7", "D12", "H8", "H11", "D10"], "E11": ["F7", "D9", "A8", "H11", "D10"], "F11": ["F7", "G7", "D11", "H8", "B9"], "G11": ["F7", "F11", "A12", "D11", "G8"], "H11": ["F7", "D8", "D11", "A9", "B9"]}
+#tiprack_num=5
+
+# __LABWARES is expected to be redefined by "generate_ot2_script" method
+# Test dict
+# __LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+
+final_assembly_dict={"A1": ["A7", "B7", "C7", "D7", "E7"], "B1": ["A7", "F7", "G7", "H7", "A8"], "C1": ["A7", "B8", "C8", "D8", "E8"]}
+tiprack_num=1
+__LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+    def final_assembly(final_assembly_dict, tiprack_num, tiprack_type=__LABWARES['96_tiprack_20ul']['id']):
+            # Constants, we update all the labware name in version 2
+            #Tiprack
+            CANDIDATE_TIPRACK_SLOTS = ['2', '3', '5', '6', '9']
+            PIPETTE_MOUNT = 'right'
+            #Plate of sample after  purification
+            MAG_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_23']['id']
+            MAG_PLATE_POSITION = '1'
+            #Tuberack
+            TUBE_RACK_TYPE = __LABWARES['24_tuberack_1500ul']['id']
+            TUBE_RACK_POSITION = '4'
+            #Destination plate
+            DESTINATION_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_23']['id']
+            TOTAL_VOL = 15
+            PART_VOL = 1.5
+            MIX_SETTINGS = (1, 3)
+            tiprack_num=tiprack_num+1
+            # Errors
+            sample_number = len(final_assembly_dict.keys())
+            if sample_number > 96:
+                raise ValueError('Final assembly nummber cannot exceed 96.')
+
+            slots = CANDIDATE_TIPRACK_SLOTS[:tiprack_num]
+            tipracks = [protocol.load_labware(tiprack_type, slot) for slot in slots]
+            pipette = protocol.load_instrument(__LABWARES['p20_single']['id'], PIPETTE_MOUNT, tip_racks=tipracks)
+
+            # Define Labware and set temperature
+            magbead_plate = protocol.load_labware(MAG_PLATE_TYPE, MAG_PLATE_POSITION)
+            tube_rack = protocol.load_labware(TUBE_RACK_TYPE, TUBE_RACK_POSITION)
+            
+            
+            #Thermocycler Module
+            tc_mod = protocol.load_module('Thermocycler Module')
+            destination_plate = tc_mod.load_labware(DESTINATION_PLATE_TYPE)
+            tc_mod.set_block_temperature(20)
+
+
+             # Master mix transfers
+            final_assembly_lens = []
+            for values in final_assembly_dict.values():
+                final_assembly_lens.append(len(values))
+            unique_assemblies_lens = list(set(final_assembly_lens))
+            master_mix_well_letters = ['A', 'B', 'C', 'D']
+            
+            for x in unique_assemblies_lens:
+                master_mix_well = master_mix_well_letters[(x - 1) // 6] + str(x - 1)
+                destination_inds = [i for i, lens in enumerate(final_assembly_lens) if lens == x]
+                destination_wells = np.array([key for key, value in list(final_assembly_dict.items())])
+                destination_wells = list(destination_wells[destination_inds])
+                
+                pipette.pick_up_tip()
+                for destination_well in destination_wells:# make tube_rack_wells and destination_plate.wells in the same type
+                    
+                    pipette.transfer(TOTAL_VOL - x * PART_VOL, tube_rack.wells(master_mix_well),
+                                     destination_plate.wells(destination_well), new_tip='never')#transfer water and buffer in the pipette
+
+            pipette.drop_tip()
+
+            # Part transfers
+            for key, values in list(final_assembly_dict.items()):
+                for value in values:# magbead_plate.wells and destination_plate.wells in the same type
+                    pipette.transfer(PART_VOL, magbead_plate.wells(value),
+                                     destination_plate.wells(key), mix_after=MIX_SETTINGS,
+                                     new_tip='always')#transfer parts in one tube
+
+
+
+            #Thermocycler Module
+            tc_mod.close_lid()
+            tc_mod.set_lid_temperature(105)
+            tc_mod.set_block_temperature(50, hold_time_minutes=45, block_max_volume=15)
+            tc_mod.set_block_temperature(4, hold_time_minutes=2, block_max_volume=30)
+            # Increase the hold time at 4 C if necessary
+            tc_mod.set_lid_temperature(37)
+            tc_mod.open_lid()
+
+    final_assembly(final_assembly_dict=final_assembly_dict, tiprack_num=tiprack_num)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/4_transformation_ot2_APIv1.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,318 @@
+from opentrons import labware, instruments, modules, robot
+import numpy as np
+
+
+spotting_tuples=[(('A1', 'B1', 'C1'), ('A1', 'B1', 'C1'), (5, 5, 5))]
+soc_well='A1'
+
+
+def generate_transformation_wells(spotting_tuples):
+    """Evaluates spotting_tuples and returns transformation wells.
+
+    Args:
+    spotting_tuples (list): Sets of spotting reactions are given 
+        in the form: ((source wells), (target wells), (spotting volumes)). 
+        Each unique transformation well is resuspended once prior to spotting.
+
+    """
+    wells = []
+    for spotting_tuple in spotting_tuples:
+        for source_well in spotting_tuple[0]:
+            wells.append(source_well)
+    transformation_wells = [well for i, well in enumerate(
+        wells) if wells.index(well) == i]
+    return transformation_wells
+
+
+def tiprack_slots(
+        spotting_tuples,
+        max_spot_vol=5):
+    """Calculates p10 and p300 tiprack slots required.
+
+    Args:
+    spotting_tuples (list): Sets of spotting reactions are given 
+        in the form: ((source wells), (target wells), (spotting volumes)). 
+        Each unique transformation well is resuspended once prior to spotting.
+    max_spot_vol (float): Maximum volume that is spotted per spot reaction.
+
+    """
+    # Reactions' number
+    transformation_reactions = len(
+        generate_transformation_wells(spotting_tuples))
+    spotting_reactions = 0
+    for spotting_tuple in spotting_tuples:
+        spots = np.array(spotting_tuple[2])/max_spot_vol
+        np.ceil(spots)
+        spotting_reactions = spotting_reactions + int(np.sum(spots))
+
+    # p10 tiprack slots
+    p10_tips = transformation_reactions + spotting_reactions
+    p10_tiprack_slots = p10_tips // 96 + 1 if p10_tips % 96 > 0 else p10_tips / 96
+
+    # p300 tiprack slots
+    p300_tips = transformation_reactions + spotting_reactions
+    p300_tiprack_slots = p300_tips // 96 + \
+        1 if p300_tips % 96 > 0 else p300_tips / 96
+    return int(p10_tiprack_slots), int(p300_tiprack_slots)
+
+
+def transformation_setup(transformation_wells):
+    """Sets up transformation reactions
+
+    Args:
+    transformation_wells (list). 
+
+    """
+
+    # Constants
+    TEMP = 4  # Incubation temperature.
+    ASSEMBLY_VOL = 5  # Volume of final assembly added to competent cells.
+    MIX_SETTINGS = (4, 5)  # Mix after setting during final assembly transfers.
+    INCUBATION_TIME = 20  # Cells and final assembly incubation time.
+
+    # Set temperature deck to 4 °C and load competent cells
+    tempdeck.set_temperature(TEMP)
+    tempdeck.wait_for_temp()
+    robot.pause()
+    robot.comment('Load competent cells, uncap and resume run')
+
+    # Transfer final assemblies
+    p10_pipette.transfer(ASSEMBLY_VOL,
+                         assembly_plate.wells(transformation_wells),
+                         transformation_plate.wells(
+                             transformation_wells), new_tip='always',
+                         mix_after=(MIX_SETTINGS))
+
+    # Incubate for 20 minutes and remove competent cells for heat shock
+    p10_pipette.delay(minutes=INCUBATION_TIME)
+    robot.pause()
+    robot.comment(
+        'Remove transformation reactions, conduct heatshock and replace.')
+
+
+def phase_switch(comment='Remove final assembly plate. Introduce agar tray and deep well plate containing SOC media. Resume run.'):
+    """Function pauses run enabling addition/removal of labware.
+
+    Args:
+    comment (str): string to be displayed during run following pause.
+
+    """
+    robot.pause()
+    robot.comment(comment)
+
+
+def outgrowth(
+        cols,
+        soc_well):
+    """Outgrows transformed cells.
+
+    Args:
+    cols (list of str): list of cols in transformation plate containing samples.
+    soc_well (str): Well containing SOC media in relevant plate.
+
+    """
+
+    # Constants
+    SOC_VOL = 125
+    SOC_MIX_SETTINGS = (4, 50)
+    TEMP = 37
+    OUTGROWTH_TIME = 60
+    SOC_ASPIRATION_RATE = 25
+    P300_DEFAULT_ASPIRATION_RATE = 150
+
+    # Define wells
+    transformation_cols = transformation_plate.cols(cols)
+    soc = soc_plate.wells(soc_well)
+
+    # Add SOC to transformed cells
+    p300_pipette.set_flow_rate(aspirate=SOC_ASPIRATION_RATE)
+    p300_pipette.transfer(SOC_VOL, soc, transformation_cols,
+                          new_tip='always', mix_after=SOC_MIX_SETTINGS)
+    p300_pipette.set_flow_rate(aspirate=P300_DEFAULT_ASPIRATION_RATE)
+
+    # Incubate for 1 hour at 37 °C
+    tempdeck.set_temperature(TEMP)
+    tempdeck.wait_for_temp()
+    p300_pipette.delay(minutes=OUTGROWTH_TIME)
+    tempdeck.deactivate()
+
+
+def spotting_cols(spotting_tuples):
+    """Evaluates spotting_tuples and returns unique cols (str) 
+    associated with each spotting_tuple's source wells.
+
+    Args:
+    spotting_tuples (list): Sets of spotting reactions are given 
+        in the form: ((source wells), (target wells), (spotting volumes)). 
+        Each unique transformation well is resuspended once prior to spotting.
+
+    """
+    cols_list = []
+    for spotting_tuple in spotting_tuples:
+        source_wells_cols = [source_well[1:]
+                             for source_well in spotting_tuple[0]]
+        unique_cols = [col for i, col in enumerate(
+            source_wells_cols) if source_wells_cols.index(col) == i]
+        cols_list.append(unique_cols)
+    return cols_list
+
+
+def spot_transformations(
+        spotting_tuples,
+        dead_vol=2,
+        spotting_dispense_rate=0.025,
+        stabbing_depth=10,
+        max_spot_vol=5):
+    """Spots transformation reactions.
+
+    Args:
+    spotting_tuples (list): Sets of spotting reactions are given 
+        in the form: ((source wells), (target wells), (spotting volumes)). 
+        Each unique source well is resuspended once prior to spotting.
+    dead_vol (float): Dead volume aspirated during spotting.
+    spotting_dispense_rate (float): Rate p10_pipette dispenses at during spotting.
+    stabbing_depth (float): Depth p10_pipette moves into agar during spotting.
+    max_spot_vol (float): Maximum volume that is spotted per spot reaction. 
+
+    """
+
+    def spot(
+            source,
+            target,
+            spot_vol):
+        """Spots an individual reaction using the p10 pipette.
+
+        Args:
+        source (str): Well containing the transformation reaction to be spotted.
+        target (str): Well transformation reaction is to be spotted to.
+        spot_vol (float): Volume of transformation reaction to be spotted (uL).  
+
+        """
+        # Constants
+        DEFAULT_HEAD_SPEED = {'x': 400, 'y': 400,
+                              'z': 125, 'a': 125}
+        SPOT_HEAD_SPEED = {'x': 400, 'y': 400, 'z': 125,
+                           'a': 125 // 4}
+        DISPENSING_HEIGHT = 5
+        SAFE_HEIGHT = 15  # height avoids collision with agar tray.
+
+        # Spot
+        p10_pipette.pick_up_tip()
+        p10_pipette.aspirate(spot_vol + dead_vol, source)
+        p10_pipette.move_to(target.top(SAFE_HEIGHT))
+        p10_pipette.move_to(target.top(DISPENSING_HEIGHT))
+        p10_pipette.dispense(volume=spot_vol, rate=spotting_dispense_rate)
+        robot.head_speed(combined_speed=max(
+            SPOT_HEAD_SPEED.values()), **SPOT_HEAD_SPEED)
+        p10_pipette.move_to(target.top(-1 * stabbing_depth))
+        robot.head_speed(combined_speed=max(
+            DEFAULT_HEAD_SPEED.values()), **DEFAULT_HEAD_SPEED)
+        p10_pipette.move_to(target.top(SAFE_HEIGHT))
+
+        # Dispose of dead volume and tip
+        p10_pipette.dispense(dead_vol, spotting_waste)
+        p10_pipette.blow_out()
+        p10_pipette.drop_tip()
+
+    def spot_tuple(spotting_tuple):
+        """Spots all reactions defined by the spotting tuple. Requires the function spot.
+
+            Args:
+            spotting_tuple (tuple): Spotting reactions given in the form: (source wells), (target wells), (spotting volumes).
+
+        """
+        source_wells = spotting_tuple[0]
+        target_wells = spotting_tuple[1]
+        spot_vols = list(spotting_tuple[2])
+        while max(spot_vols) > 0:
+            for index, spot_vol in enumerate(spot_vols):
+                if spot_vol == 0:
+                    pass
+                else:
+                    vol = spot_vol if spot_vol <= max_spot_vol else max_spot_vol
+                    spot(transformation_plate.wells(source_wells[index]),
+                         agar_plate.wells(target_wells[index]), vol)
+                    spot_vols[index] = spot_vols[index] - vol
+
+    # Constants
+    TRANSFORMATION_MIX_SETTINGS = [4, 50]
+
+    # Spot transformation reactions
+    for spotting_tuple in spotting_tuples:
+        source_wells_cols = [source_well[1:]
+                             for source_well in spotting_tuple[0]]
+        unique_cols = [col for i, col in enumerate(
+            source_wells_cols) if source_wells_cols.index(col) == i]
+        for col in unique_cols:
+            p300_pipette.pick_up_tip()
+            p300_pipette.mix(TRANSFORMATION_MIX_SETTINGS[0],
+                             TRANSFORMATION_MIX_SETTINGS[1],
+                             transformation_plate.cols(col))
+            p300_pipette.drop_tip()
+        spot_tuple(spotting_tuple)
+
+
+# Run protocol
+
+# Constants
+CANDIDATE_P10_SLOTS = ['9', '2', '5']
+CANDIDATE_P300_SLOTS = ['3', '6']
+P10_TIPRACK_TYPE = 'tiprack-10ul'
+P300_TIPRACK_TYPE = 'opentrons_96_tiprack_300ul'
+P10_MOUNT = 'right'
+P300_MOUNT = 'left'
+ASSEMBLY_PLATE_TYPE = '4ti-0960_FrameStar'
+ASSEMBLY_PLATE_SLOT = '8'
+TEMPDECK_SLOT = '10'
+TRANSFORMATION_PLATE_TYPE = 'Eppendorf_30133366_plate_96'
+SOC_PLATE_TYPE = '4ti0136_96_deep-well'
+SOC_PLATE_SLOT = '7'
+TUBE_RACK_TYPE = 'tube-rack_E1415-1500'
+TUBE_RACK_SLOT = '11'
+SPOTTING_WASTE_WELL = 'A1'
+AGAR_PLATE_TYPE = 'Nunc_Omnitray'
+AGAR_PLATE_SLOT = '1'
+
+# Tiprack slots
+p10_p300_tiprack_slots = tiprack_slots(spotting_tuples)
+p10_slots = CANDIDATE_P10_SLOTS[
+    :p10_p300_tiprack_slots[0]]
+p300_slots = CANDIDATE_P300_SLOTS[
+    :p10_p300_tiprack_slots[1]]
+
+# Define labware
+p10_tipracks = [labware.load(P10_TIPRACK_TYPE, slot)
+                for slot in p10_slots]
+p300_tipracks = [labware.load(P300_TIPRACK_TYPE, slot)
+                 for slot in p300_slots]
+p10_pipette = instruments.P10_Single(
+    mount=P10_MOUNT, tip_racks=p10_tipracks)
+p300_pipette = instruments.P300_Multi(
+    mount=P300_MOUNT, tip_racks=p300_tipracks)
+
+assembly_plate = labware.load(ASSEMBLY_PLATE_TYPE, ASSEMBLY_PLATE_SLOT)
+tempdeck = modules.load('tempdeck', TEMPDECK_SLOT)
+transformation_plate = labware.load(TRANSFORMATION_PLATE_TYPE,
+                                    TEMPDECK_SLOT, share=True)
+soc_plate = labware.load(SOC_PLATE_TYPE, SOC_PLATE_SLOT)
+tube_rack = labware.load(TUBE_RACK_TYPE, TUBE_RACK_SLOT)
+spotting_waste = tube_rack.wells(SPOTTING_WASTE_WELL)
+agar_plate = labware.load(AGAR_PLATE_TYPE, AGAR_PLATE_SLOT)
+
+# Register agar_plate for calibration
+p10_pipette.transfer(1, agar_plate.wells(
+    'A1'), agar_plate.wells('H12'), trash=False)
+p10_pipette.start_at_tip(p10_tipracks[0][0])
+
+# Run functions
+transformation_setup(generate_transformation_wells(spotting_tuples))
+phase_switch()
+spotting_tuples_cols = [col for cols in spotting_cols(
+    spotting_tuples) for col in cols]
+unique_cols = [col for i, col in enumerate(
+    spotting_tuples_cols) if spotting_tuples_cols.index(col) == i]
+outgrowth(unique_cols, soc_well=soc_well)
+spot_transformations(spotting_tuples)
+
+for c in robot.commands():
+    print(c)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/4_transformation_ot2_APIv2.8.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,422 @@
+from opentrons import protocol_api
+import numpy as np
+
+
+# Rename to 'purification_template' and paste into 'template_ot2_scripts' folder in DNA-BOT to use
+
+
+metadata = {
+     'apiLevel': '2.8',
+     'protocolName': 'Transformation',
+     'description': 'Transformation reactions using an opentrons OT-2 for BASIC assembly.'}
+
+# Example output produced by DNA-BOT for a single construct, uncomment and run to test the template
+#spotting_tuples=[(('A1','B1','C1'), ('A1','B1', 'C1'), (5,5,5))]
+#soc_well='A1'
+
+# Example output produced by DNA-BOT for 88 constructs, uncomment and run to test the template
+#spotting_tuples=[(('A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1'), ('A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2'), ('A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3'), ('A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4'), ('A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5'), ('A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6'), ('A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7'), ('A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8'), ('A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9'), ('A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A10', 'B10', 'C10', 'D10', 'E10', 'F10', 'G10', 'H10'), ('A10', 'B10', 'C10', 'D10', 'E10', 'F10', 'G10', 'H10'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A11', 'B11', 'C11', 'D11', 'E11', 'F11', 'G11', 'H11'), ('A11', 'B11', 'C11', 'D11', 'E11', 'F11', 'G11', 'H11'), (5, 5, 5, 5, 5, 5, 5, 5))]
+#soc_well='A1'
+
+# __LABWARES is expected to be redefined by "generate_ot2_script" method
+# Test dict
+# __LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+# __PARAMETERS={"purif_magdeck_height": {"value": 20.0}, "purif_wash_time": {"value": 0.5}, "purif_bead_ratio": {"value": 1.8}, "purif_incubation_time": {"value": 5.0}, "purif_settling_time": {"value": 2.0}, "purif_drying_time": {"value": 5.0}, "purif_elution_time": {"value": 2.0}, "transfo_incubation_temp": {"value": 4.0}, "transfo_incubation_time": {"value": 20.0}}
+
+spotting_tuples=[(('A1', 'B1', 'C1'), ('A1', 'B1', 'C1'), (5, 5, 5))]
+soc_well='A1'
+__LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+__PARAMETERS={"purif_magdeck_height": {"value": 20}, "purif_wash_time": {"value": 0}, "purif_bead_ratio": {"value": 1}, "purif_incubation_time": {"value": 5}, "purif_settling_time": {"value": 2}, "purif_drying_time": {"value": 5}, "purif_elution_time": {"value": 2}, "transfo_incubation_temp": {"value": 4}, "transfo_incubation_time": {"value": 20}}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+# added run function for API version 2
+
+# Constants
+    CANDIDATE_p20_SLOTS = ['9', '2', '5']
+    CANDIDATE_P300_SLOTS = ['3', '6']
+    p20_TIPRACK_TYPE = __LABWARES['96_tiprack_20ul']['id']
+        # changed from 'tiprack-10ul'
+    P300_TIPRACK_TYPE = __LABWARES['96_tiprack_300ul']['id']
+    P20_MOUNT = 'right'
+    P300_MOUNT = 'left'
+    ASSEMBLY_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+    ASSEMBLY_PLATE_SLOT = '8'
+
+    TRANSFORMATION_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+    SOC_PLATE_TYPE = __LABWARES['96_deepwellplate_2ml']['id']
+    SOC_PLATE_SLOT = '7'
+    TUBE_RACK_TYPE = __LABWARES['24_tuberack_1500ul']['id']
+    TUBE_RACK_SLOT = '11'
+    SPOTTING_WASTE_WELL = 'A1'
+    AGAR_PLATE_TYPE = __LABWARES['agar_plate_step_4']['id']
+        # changed from 'Nunc_Omnitray'
+            # it is a 1 well plate filled with agar;
+            # but for the Opentron to spot in the locations of a 96 wp, it is defined similar to a 96 wp
+            # was previously defined in add.labware.py, API version 2 doesn't support labware.create anymore
+
+
+
+        # custom labware made using Opentron's Labware Creator:
+            # external dimensions:
+                # footprint length = 127.76 mm
+                # footrpint width = 85.48 mm
+                # footprint height = 15.70 mm
+                # taken from Thermofisher's documentation for Nunc Omnitray
+                # https://www.thermofisher.com/document-connect/document-connect.html?url=https%3A%2F%2Fassets.thermofisher.com%2FTFS-Assets%2FLSG%2Fmanuals%2FD03023.pdf&title=VGVjaG5pY2FsIERhdGEgU2hlZXQ6IE51bmMgT21uaXRyYXk=
+            # well measurements
+                # depth = 0.01 mm
+                # diameter =  0.01 mm
+                # in old add.labware.py, they were defined as 0, but Labware Creator requires a value >0
+            # spacing
+                # x-offset = 14.38 mm
+                # y-offset = 11.24 mm
+                # x-spacing = 9.00 mm
+                # y-spacing) = 9.00 mm
+                # taken from Nest 96 well plates
+                # https://labware.opentrons.com/nest_96_wellplate_100ul_pcr_full_skirt/
+        # before using protocol, need to upload the 'nuncomnitray_96_wellplate_0.01ul.json' custom labware file into Opentrons app
+
+    AGAR_PLATE_SLOT = '1'
+
+    TEMPDECK_SLOT = '10'
+    
+
+    
+    def generate_transformation_wells(spotting_tuples):
+        """
+        Evaluates spotting_tuples and returns transformation wells.
+
+        Args:
+        spotting_tuples (list): Sets of spotting reactions are given in the form: ((source wells), (target wells), (spotting volumes)).
+
+        """
+
+        wells = []
+        for spotting_tuple in spotting_tuples:
+            for source_well in spotting_tuple[0]:
+                wells.append(source_well)
+        transformation_wells = [well for i, well in enumerate(
+            wells) if wells.index(well) == i]
+        return transformation_wells
+
+
+    def tiprack_slots(spotting_tuples, max_spot_vol=5):
+        """
+        Calculates p20 and p300 tiprack slots required.
+
+        Args:
+        spotting_tuples (list): Sets of spotting reactions are given in the form: ((source wells), (target wells), (spotting volumes)).
+        max_spot_vol (float): Maximum volume that is spotted per spot reaction.
+
+        """
+
+        # Reactions' number
+        transformation_reactions = len(generate_transformation_wells(spotting_tuples))
+        spotting_reactions = 0
+        for spotting_tuple in spotting_tuples:
+            spots = np.array(spotting_tuple[2])/max_spot_vol
+            np.ceil(spots)
+            spotting_reactions = spotting_reactions + int(np.sum(spots))
+
+        # errrr should be fine lol # REMOVE
+
+        # p20 tiprack slots
+        p20_tips = transformation_reactions + spotting_reactions
+        p20_tiprack_slots = p20_tips // 96 + 1 if p20_tips % 96 > 0 else p20_tips / 96
+
+        # p300 tiprack slots
+        p300_tips = transformation_reactions + spotting_reactions
+        p300_tiprack_slots = p300_tips // 96 + \
+            1 if p300_tips % 96 > 0 else p300_tips / 96
+        return int(p20_tiprack_slots), int(p300_tiprack_slots)
+
+
+    def transformation_setup(transformation_wells):
+        """
+        Sets up transformation reactions
+
+        Args:
+        transformation_wells (list).
+
+        """
+
+        # Constants
+        TEMP = __PARAMETERS['transfo_incubation_temp']['value']  # Incubation temperature.
+        ASSEMBLY_VOL = 5  # Volume of final assembly added to competent cells.
+        MIX_SETTINGS = (4, 5)  # Mix after setting during final assembly transfers.
+        INCUBATION_TIME = __PARAMETERS['transfo_incubation_time']['value']  # Cells and final assembly incubation time.
+
+
+
+        # Set temperature deck to 4 °C and load competent cells
+        tempdeck.set_temperature(TEMP)
+        # removed: tempdeck.wait_for_temp()
+            # API version2 automatically pauses execution until the set temperature is reached
+            # thus it no longer uses .wait_for_temp()
+        protocol.pause('Load competent cells, uncap and resume run')
+
+        # Transfer final assemblies
+        p20_pipette.transfer(ASSEMBLY_VOL,
+                             [assembly_plate.wells_by_name()[well_name] for well_name in transformation_wells],
+                             [transformation_plate.wells_by_name()[well_name] for well_name in transformation_wells],
+                             new_tip='always',
+                             mix_after=(MIX_SETTINGS))
+
+
+        # Incubate for 20 minutes and remove competent cells for heat shock
+        protocol.delay(minutes=INCUBATION_TIME)
+
+
+        protocol.pause('Remove transformation reactions, conduct heatshock and replace.')
+
+
+    def phase_switch():
+        """
+        Function pauses run enabling addition/removal of labware.
+
+        """
+        protocol.pause('Remove final assembly plate. Introduce agar tray and deep well plate containing SOC media. Resume run.')
+
+    def outgrowth(
+            cols,
+            soc_well):
+        """
+        Outgrows transformed cells.
+
+        Args:
+        cols (list of str): list of cols in transformation plate containing samples.
+        soc_well (str): Well containing SOC media in relevant plate.
+
+        """
+
+        # Constants
+        SOC_VOL = 125
+        SOC_MIX_SETTINGS = (4, 50)
+        TEMP = 37
+        OUTGROWTH_TIME = 60
+        SOC_ASPIRATION_RATE = 25
+        P300_DEFAULT_ASPIRATION_RATE = 150
+
+        # Define wells
+        transformation_cols = [transformation_plate.columns_by_name()[column] for column in cols]
+
+        soc = soc_plate.wells(soc_well)
+
+        # Add SOC to transformed cells
+        p300_pipette.flow_rate.aspirate = SOC_ASPIRATION_RATE
+        p300_pipette.transfer(SOC_VOL, soc, transformation_cols,
+                              new_tip='always', mix_after=SOC_MIX_SETTINGS)
+        p300_pipette.flow_rate.aspirate = P300_DEFAULT_ASPIRATION_RATE
+
+        # Incubate for 1 hour at 37 °C
+        tempdeck.set_temperature(TEMP)
+
+        protocol.delay(minutes=OUTGROWTH_TIME)
+
+
+        tempdeck.deactivate()
+
+
+    def spotting_cols(spotting_tuples):
+        """
+        Evaluates spotting_tuples and returns unique cols (str) associated with each spotting_tuple's source wells.
+
+        Args:
+        spotting_tuples (list): Sets of spotting reactions are given in the form: ((source wells), (target wells), (spotting volumes)).
+
+        """
+        cols_list = []
+        for spotting_tuple in spotting_tuples:
+            source_wells_cols = [source_well[1:] for source_well in spotting_tuple[0]]
+            unique_cols = [col for i, col in enumerate(source_wells_cols) if source_wells_cols.index(col) == i]
+            cols_list.append(unique_cols)
+        return cols_list
+
+
+    def spot_transformations(
+            spotting_tuples,
+            dead_vol=1,
+            spotting_dispense_rate=0.025,
+            stabbing_depth=10,
+            max_spot_vol=5):
+        """
+        Spots transformation reactions.
+
+        Args:
+        spotting_tuples (list): Sets of spotting reactions are given in the form: ((source wells), (target wells), (spotting volumes)).
+        dead_vol (float): Dead volume aspirated during spotting.
+        spotting_dispense_rate (float): Rate p20_pipette dispenses at during spotting.
+        stabbing_depth (float): Depth p20_pipette moves into agar during spotting.
+        max_spot_vol (float): Maximum volume that is spotted per spot reaction.
+
+        """
+
+        def spot(
+                source,
+                target,
+                spot_vol):
+            """
+            Spots an individual reaction using the p20 pipette.
+
+            Args:
+            source (str): Well containing the transformation reaction to be spotted.
+            target (str): Well transformation reaction is to be spotted to.
+            spot_vol (float): Volume of transformation reaction to be spotted (uL).
+
+            """
+
+            # Constants
+            DEFAULT_HEAD_SPEED = {'x': 400, 'y': 400,'z': 125, 'a': 125}
+            SPOT_HEAD_SPEED = {'x': 400, 'y': 400, 'z': 125,'a': 125 // 4}
+            DISPENSING_HEIGHT = 5
+            SAFE_HEIGHT = 7  # height avoids collision with agar tray.
+
+            # Spot
+            p20_pipette.pick_up_tip()
+            p20_pipette.aspirate(spot_vol + dead_vol, source[0])
+            # old code:
+                # p20_pipette.aspirate(spot_vol + dead_vol, source)
+                # returned type error because 'source' was a list containing one item (the well location)
+                # source[0] takes the location out of the list
+
+            p20_pipette.move_to(target[0].top(SAFE_HEIGHT))
+            p20_pipette.move_to(target[0].top(DISPENSING_HEIGHT))
+            # old code:
+                # p20_pipette.move_to(target.top(SAFE_HEIGHT))
+                # p20_pipette.move_to(target.top(DISPENSING_HEIGHT))
+                # returned attribute error because 'target' was a list containing one item (the well location)
+                # target[0] takes the location out of the list
+
+            p20_pipette.dispense(volume=spot_vol, rate=spotting_dispense_rate)
+
+            protocol.max_speeds.update(SPOT_HEAD_SPEED)
+            # old code:
+                # robot.head_speed(combined_speed=max(SPOT_HEAD_SPEED.values()), **SPOT_HEAD_SPEED)
+                # robot.head_speed not used in API version 2
+                # replaced with protocol.max_speeds
+            # new code no longer uses the lower value between combined speed or specified speed
+                # just uses each axis' specified speed directly
+            p20_pipette.move_to(target[0].top(-1 * stabbing_depth))
+            # old code:
+                # p20_pipette.move_to(target.top(-1*stabbing_depth))
+                # returns attribute error because 'target' was a list containing one item (the well location)
+            protocol.max_speeds.update(DEFAULT_HEAD_SPEED)
+            # old code:
+                # robot.head_speed(combined_speed=max(DEFAULT_HEAD_SPEED.values()), **DEFAULT_HEAD_SPEED)
+                # robot.head_speed not used in API version 2
+                # replaced with protocol.max_speeds
+            # new code no longer uses the lower value between combined speed or specified speed
+                # just uses each axis' specified speed directly
+            p20_pipette.move_to(target[0].top(SAFE_HEIGHT))
+            # old code:
+                # p20_pipette.move_to(target[0].top(SAFE_HEIGHT))
+                # returns attribute error because 'target' was a list containing one item (the well location)
+
+
+            # the code below makes sure that the transformend cells are efficiently reaching to the agar surface
+
+            p20_pipette.blow_out()
+
+            protocol.delay(seconds=10)
+
+            p20_pipette.blow_out()
+
+            protocol.delay(seconds=10)
+
+            # Dispose of dead volume and tip
+            p20_pipette.dispense(dead_vol, spotting_waste[0])
+
+            p20_pipette.blow_out()
+                # the simple .blow_out command blows out at current position (spotting waste) by defualt
+                # unlike blowout=true in complex commands, which by default will blow out in waste
+
+            p20_pipette.drop_tip()
+
+        def spot_tuple(spotting_tuple):
+            """
+            Spots all reactions defined by the spotting tuple. Requires the function spot.
+
+            Args:
+            spotting_tuple (tuple): Spotting reactions given in the form: (source wells), (target wells), (spotting volumes).
+            Each unique source well is resuspended once prior to spotting.
+
+            """
+            source_wells = spotting_tuple[0]
+            target_wells = spotting_tuple[1]
+            spot_vols = list(spotting_tuple[2])
+            while max(spot_vols) > 0:
+                for index, spot_vol in enumerate(spot_vols):
+                    if spot_vol == 0:
+                        pass
+                    else:
+                        vol = spot_vol if spot_vol <= max_spot_vol else max_spot_vol
+                        spot(source = transformation_plate.wells(source_wells[index]), target = agar_plate.wells(target_wells[index]), spot_vol = vol)
+                        spot_vols[index] = spot_vols[index] - vol
+
+        # Constants
+        TRANSFORMATION_MIX_SETTINGS = [4, 50]
+
+        # Spot transformation reactions
+            # Each unique transformation well is resuspended once prior to spotting.
+
+        for spotting_tuple in spotting_tuples:
+            source_wells_cols = [source_well[1:] for source_well in spotting_tuple[0]]
+            unique_cols = [col for i, col in enumerate(source_wells_cols) if source_wells_cols.index(col) == i]
+            for col in unique_cols:
+                p300_pipette.pick_up_tip()
+                p300_pipette.mix(TRANSFORMATION_MIX_SETTINGS[0], TRANSFORMATION_MIX_SETTINGS[1],transformation_plate.columns_by_name()[col][0])
+                p300_pipette.drop_tip()
+            spot_tuple(spotting_tuple)
+
+     #Tiprack slots
+    p20_p300_tiprack_slots = tiprack_slots(spotting_tuples)
+    p20_slots = CANDIDATE_p20_SLOTS[:p20_p300_tiprack_slots[0]]
+    p300_slots = CANDIDATE_P300_SLOTS[:p20_p300_tiprack_slots[1]]
+    # Define labware
+    p20_tipracks = [protocol.load_labware(p20_TIPRACK_TYPE, slot) for slot in p20_slots]
+        # changed to protocol.load_labware for API version 2
+    p300_tipracks = [protocol.load_labware(P300_TIPRACK_TYPE, slot) for slot in p300_slots]
+        # changed to protocol.load_labware for API version 2
+    p20_pipette = protocol.load_instrument(__LABWARES['p20_single']['id'], P20_MOUNT, tip_racks=p20_tipracks)
+        # changed to protocol.load_instrument for API version 2
+    p300_pipette = protocol.load_instrument(__LABWARES['p300_multi']['id'], P300_MOUNT, tip_racks=p300_tipracks)
+        # changed to protocol.load_instrument for API version 2
+    assembly_plate = protocol.load_labware(ASSEMBLY_PLATE_TYPE, ASSEMBLY_PLATE_SLOT)
+        # changed to protocol.load_labware for API version 2
+    tempdeck = protocol.load_module('tempdeck', TEMPDECK_SLOT)
+    transformation_plate = tempdeck.load_labware(TRANSFORMATION_PLATE_TYPE, TEMPDECK_SLOT)
+        # changed to protocol.load_labware for API version 2
+        # removed share=True, not required in API version 2
+        # removed TEMPDECK_SLOT as it is loaded directly onto temperature module
+    soc_plate = protocol.load_labware(SOC_PLATE_TYPE, SOC_PLATE_SLOT)
+        # changed to protocol.load_labware for API version 2
+    tube_rack = protocol.load_labware(TUBE_RACK_TYPE, TUBE_RACK_SLOT)
+        # changed to protocol.load_labware for API version 2
+    spotting_waste = tube_rack.wells(SPOTTING_WASTE_WELL)
+    agar_plate = protocol.load_labware(AGAR_PLATE_TYPE, AGAR_PLATE_SLOT)
+        # changed to protocol.load_labware for API version 2
+    
+    ### Run protocol
+
+
+    # Register agar_plate for calibration
+    p20_pipette.transfer(1, agar_plate.wells('A1'), agar_plate.wells('H12'), trash=False)
+    # removed:
+        # p20_pipette.start_at_tip(p20_tipracks[0][0])
+        # pipette automatically starts from 'A1' tiprack location
+        # if re-adding, need to use p20.pipette.starting_tip() instead of p20.pipette.start_at_tip()
+
+
+    # Run functions
+    # Run functions
+    transformation_setup(generate_transformation_wells(spotting_tuples))
+    phase_switch()
+    spotting_tuples_cols = [col for cols in spotting_cols(spotting_tuples) for col in cols]
+    unique_cols = [col for i, col in enumerate(spotting_tuples_cols) if spotting_tuples_cols.index(col) == i]
+    outgrowth(cols=unique_cols, soc_well=soc_well)
+    spot_transformations(spotting_tuples)
+    # Spot again to increase the chances of getting more colonies. Second spotting will only work for small number of assemblies where there is enough space on the deck for tipracks.
+    # protocol.pause('Let the spotted cells get dry')
+    # spot_transformations(spotting_tuples)
+    print(unique_cols)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/4_transformation_ot2_Thermocycler_APIv2.8.py	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,500 @@
+from opentrons import protocol_api
+import numpy as np
+
+
+# Rename to 'purification_template' and paste into 'template_ot2_scripts' folder in DNA-BOT to use
+# Tip rack positions are limited for this script, up to 48 transformations can be performed.
+
+metadata = {
+     'apiLevel': '2.8',
+     'protocolName': 'Transformation',
+     'description': 'Transformation reactions using an opentrons OT-2 for BASIC assembly.'}
+
+# Example output produced by DNA-BOT for a single construct, uncomment and run to test the template
+#spotting_tuples=[(('A1','B1','C1'), ('A1','B1', 'C1'), (8,8,8))]
+#soc_well='A1'
+
+# Example output produced by DNA-BOT for 88 constructs, uncomment and run to test the template
+#spotting_tuples=[(('A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1'), ('A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2'), ('A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3'), ('A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4'), ('A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5'), ('A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6'), ('A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7'), ('A7', 'B7', 'C7', 'D7', 'E7', 'F7', 'G7', 'H7'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8'), ('A8', 'B8', 'C8', 'D8', 'E8', 'F8', 'G8', 'H8'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9'), ('A9', 'B9', 'C9', 'D9', 'E9', 'F9', 'G9', 'H9'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A10', 'B10', 'C10', 'D10', 'E10', 'F10', 'G10', 'H10'), ('A10', 'B10', 'C10', 'D10', 'E10', 'F10', 'G10', 'H10'), (5, 5, 5, 5, 5, 5, 5, 5)), (('A11', 'B11', 'C11', 'D11', 'E11', 'F11', 'G11', 'H11'), ('A11', 'B11', 'C11', 'D11', 'E11', 'F11', 'G11', 'H11'), (5, 5, 5, 5, 5, 5, 5, 5))]
+#soc_well='A1'
+
+# __LABWARES is expected to be redefined by "generate_ot2_script" method
+# Test dict
+# __LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+# __PARAMETERS={"purif_magdeck_height": {"value": 20.0}, "purif_wash_time": {"value": 0.5}, "purif_bead_ratio": {"value": 1.8}, "purif_incubation_time": {"value": 5.0}, "purif_settling_time": {"value": 2.0}, "purif_drying_time": {"value": 5.0}, "purif_elution_time": {"value": 2.0}, "transfo_incubation_temp": {"value": 4.0}, "transfo_incubation_time": {"value": 20.0}}
+
+spotting_tuples=[(('A1', 'B1', 'C1'), ('A1', 'B1', 'C1'), (5, 5, 5))]
+soc_well='A1'
+__LABWARES={"p20_single": {"id": "p20_single_gen2"}, "p300_multi": {"id": "p300_multi_gen2"}, "mag_deck": {"id": "magdeck"}, "96_tiprack_20ul": {"id": "opentrons_96_tiprack_20ul"}, "96_tiprack_300ul": {"id": "opentrons_96_tiprack_300ul"}, "24_tuberack_1500ul": {"id": "e14151500starlab_24_tuberack_1500ul"}, "96_wellplate_200ul_pcr_step_14": {"id": "4ti0960rig_96_wellplate_200ul"}, "96_wellplate_200ul_pcr_step_23": {"id": "4ti0960rig_96_wellplate_200ul"}, "agar_plate_step_4": {"id": "4ti0960rig_96_wellplate_200ul"}, "12_reservoir_21000ul": {"id": "4ti0131_12_reservoir_21000ul"}, "96_deepwellplate_2ml": {"id": "4ti0136_96_wellplate_2200ul"}}
+__PARAMETERS={"purif_magdeck_height": {"value": 20}, "purif_wash_time": {"value": 0}, "purif_bead_ratio": {"value": 1}, "purif_incubation_time": {"value": 5}, "purif_settling_time": {"value": 2}, "purif_drying_time": {"value": 5}, "purif_elution_time": {"value": 2}, "transfo_incubation_temp": {"value": 4}, "transfo_incubation_time": {"value": 20}}
+
+
+def run(protocol: protocol_api.ProtocolContext):
+# added run function for API version 2
+
+    # Constants
+    CANDIDATE_p20_SLOTS = ['3']
+    CANDIDATE_P300_SLOTS = ['6']
+    P20_TIPRACK_TYPE = __LABWARES['96_tiprack_10ul']['id']
+    P300_TIPRACK_TYPE = __LABWARES['96_tiprack_300ul']['id']
+    P20_MOUNT = 'right'
+    P300_MOUNT = 'left'
+    ASSEMBLY_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+    ASSEMBLY_PLATE_SLOT = '2'
+
+    TRANSFORMATION_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+    SOC_PLATE_TYPE = __LABWARES['96_deepwellplate_2ml']['id']
+        # changed from '4ti0136_96_deep-well'
+    SOC_PLATE_SLOT = '5'
+    TUBE_RACK_TYPE = __LABWARES['24_tuberack_1500ul']['id']
+        # changed from 'tube-rack_E1415-1500'
+    TUBE_RACK_SLOT = '9'
+    SPOTTING_WASTE_WELL = 'A1'
+    AGAR_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+        # changed from 'Nunc_Omnitray'
+            # it is a 1 well plate filled with agar;
+            # but for the Opentron to spot in the locations of a 96 wp, it is defined similar to a 96 wp
+            # was previously defined in add.labware.py, API version 2 doesn't support labware.create anymore
+
+        # !!! CURRENT PLATE IS A PLACEHOLDER FOR RUNNING SIMMULATION WITHOUT CUSTOM LABWARE!!!
+        # !!! name made with Opentron Labware Creator = 'nuncomnitray_96_wellplate_0.01ul' !!!!
+
+
+        # custom labware made using Opentron's Labware Creator:
+            # external dimensions:
+                # footprint length = 127.76 mm
+                # footrpint width = 85.48 mm
+                # footprint height = 15.70 mm
+                # taken from Thermofisher's documentation for Nunc Omnitray
+                # https://www.thermofisher.com/document-connect/document-connect.html?url=https%3A%2F%2Fassets.thermofisher.com%2FTFS-Assets%2FLSG%2Fmanuals%2FD03023.pdf&title=VGVjaG5pY2FsIERhdGEgU2hlZXQ6IE51bmMgT21uaXRyYXk=
+            # well measurements
+                # depth = 0.01 mm
+                # diameter =  0.01 mm
+                # in old add.labware.py, they were defined as 0, but Labware Creator requires a value >0
+            # spacing
+                # x-offset = 14.38 mm
+                # y-offset = 11.24 mm
+                # x-spacing = 9.00 mm
+                # y-spacing) = 9.00 mm
+                # taken from Nest 96 well plates
+                # https://labware.opentrons.com/nest_96_wellplate_100ul_pcr_full_skirt/
+        # before using protocol, need to upload the 'nuncomnitray_96_wellplate_0.01ul.json' custom labware file into Opentrons app
+
+    AGAR_PLATE_SLOT = '1'
+
+    TEMPDECK_SLOT = '4'
+
+    
+    def generate_transformation_wells(spotting_tuples):
+        """
+        Evaluates spotting_tuples and returns transformation wells.
+
+        Args:
+        spotting_tuples (list): Sets of spotting reactions are given in the form: ((source wells), (target wells), (spotting volumes)).
+
+        """
+
+        wells = []
+        for spotting_tuple in spotting_tuples:
+            for source_well in spotting_tuple[0]:
+                wells.append(source_well)
+        transformation_wells = [well for i, well in enumerate(
+            wells) if wells.index(well) == i]
+        return transformation_wells
+
+
+    def tiprack_slots(spotting_tuples, max_spot_vol=5):
+        """
+        Calculates p20 and p300 tiprack slots required.
+
+        Args:
+        spotting_tuples (list): Sets of spotting reactions are given in the form: ((source wells), (target wells), (spotting volumes)).
+        max_spot_vol (float): Maximum volume that is spotted per spot reaction.
+
+        """
+
+        # Reactions' number
+        transformation_reactions = len(generate_transformation_wells(spotting_tuples))
+        spotting_reactions = 0
+        for spotting_tuple in spotting_tuples:
+            spots = np.array(spotting_tuple[2])/max_spot_vol
+            np.ceil(spots)
+            spotting_reactions = spotting_reactions + int(np.sum(spots))
+
+        # errrr should be fine lol # REMOVE
+
+        # p20 tiprack slots
+        p20_tips = transformation_reactions + spotting_reactions
+        p20_tiprack_slots = p20_tips // 96 + 1 if p20_tips % 96 > 0 else p20_tips / 96
+
+        # p300 tiprack slots
+        p300_tips = transformation_reactions + spotting_reactions
+        p300_tiprack_slots = p300_tips // 96 + \
+            1 if p300_tips % 96 > 0 else p300_tips / 96
+        return int(p20_tiprack_slots), int(p300_tiprack_slots)
+
+
+    def transformation_setup(transformation_wells):
+        """
+        Sets up transformation reactions
+
+        Args:
+        transformation_wells (list).
+
+        """
+
+        # Constants
+        TEMP = __PARAMETERS['transfo_incubation_temp']['value']  # Incubation temperature.
+        ASSEMBLY_VOL = 5  # Volume of final assembly added to competent cells.
+        MIX_SETTINGS = (4, 5)  # Mix after setting during final assembly transfers.
+        INCUBATION_TIME = __PARAMETERS['transfo_incubation_time']['value']  # Cells and final assembly incubation time.
+
+
+
+        # Set temperature deck to 4 °C and load competent cells
+        tempdeck.set_temperature(TEMP)
+        # removed: tempdeck.wait_for_temp()
+            # API version2 automatically pauses execution until the set temperature is reached
+            # thus it no longer uses .wait_for_temp()
+        protocol.pause('Load competent cells, uncap and resume run')
+        # old code:
+            # robot.pause()
+            # robot.comment('Load competent cells, uncap and resume run')
+        # API version 2 uses 'protocol.' instead of 'robot.' and combines '.pause' and '.comment'
+
+        # Transfer final assemblies
+        p20_pipette.transfer(ASSEMBLY_VOL,
+                             [assembly_plate.wells_by_name()[well_name] for well_name in transformation_wells],
+                             [transformation_plate.wells_by_name()[well_name] for well_name in transformation_wells],
+                             new_tip='always',
+                             mix_after=(MIX_SETTINGS))
+        # old code:
+            # p20_pipette.transfer(ASSEMBLY_VOL,
+                                 # assembly_plate.wells(transformation_wells),
+                                 # transformation_plate.wells(transformation_wells),
+                                 # new_tip='always',
+                                 # mix_after=(MIX_SETTINGS))
+         # .wells() doesn't take lists as arguements, newer wells_by_name() returns a dictionary
+
+
+        # Incubate for 20 minutes and remove competent cells for heat shock
+        protocol.delay(minutes=INCUBATION_TIME)
+        # old code:
+            # p20_pipette.delay(minutes=INCUBATION_TIME)
+        # API version 2 no longer has .delay() for pipettes, it uses protocol.delay() to pause the entire protocol
+
+        protocol.pause('Get ready for heat shock when thermocycler reaches to 42 C.')
+        # old code:
+            # robot.pause()
+            # robot.comment('Remove transformation reactions, conduct heatshock in the next heat block and and return the plate.')
+        # API version 2 uses 'protocol.' instead of 'robot.' and combines '.pause' and '.comment'
+
+    def heat_shock():
+
+
+        #Thermocycler Module
+        tc_mod = protocol.load_module('Thermocycler Module')
+        # Destination Plates
+        DESTINATION_PLATE_TYPE = __LABWARES['96_wellplate_200ul_pcr_step_14']['id']
+        # Loads destination plate onto Thermocycler Module
+        destination_plate = tc_mod.load_labware(DESTINATION_PLATE_TYPE)
+        tc_mod.set_block_temperature(42, hold_time_minutes=1, block_max_volume=180)
+        protocol.pause('Place the competent cells on thermocycler and resume run to conduct heat shock.')
+        protocol.delay(seconds=45)
+
+
+
+
+    def phase_switch():
+        """
+        Function pauses run enabling addition/removal of labware.
+
+        """
+        protocol.pause('Return the transformants to heat block. Remove final assembly plate. Introduce agar tray and deep well plate containing SOC media. Resume run.')
+        # old code:
+            # def phase_switch(comment='Remove final assembly plate. Introduce agar tray and deep well plate containing SOC media. Resume run.'):
+                # robot.pause()
+                # robot.comment(comment)
+        # API version 2 uses 'protocol.' instead of 'robot.' and combines '.pause' and '.comment'
+
+
+    def outgrowth(
+            cols,
+            soc_well):
+        """
+        Outgrows transformed cells.
+
+        Args:
+        cols (list of str): list of cols in transformation plate containing samples.
+        soc_well (str): Well containing SOC media in relevant plate.
+
+        """
+
+        # Constants
+        SOC_VOL = 125
+        SOC_MIX_SETTINGS = (4, 50)
+        TEMP = 37
+        OUTGROWTH_TIME = 60
+        SOC_ASPIRATION_RATE = 25
+        P300_DEFAULT_ASPIRATION_RATE = 150
+
+        # Define wells
+        transformation_cols = [transformation_plate.columns_by_name()[column] for column in cols]
+        # old code:
+            # transformation_cols = transformation_plate.cols(cols)
+        # API version 2 labware use .columns attribute, not .cols
+        # but .columns() doesn't take lists as arguements, newer columns_by_name() returns a dictionary
+
+        soc = soc_plate.wells(soc_well)
+
+        # Add SOC to transformed cells
+        p300_pipette.flow_rate.aspirate = SOC_ASPIRATION_RATE
+        # old code:
+            # p300_pipette.set_flow_rate(aspirate=SOC_ASPIRATION_RATE)
+            # flow rates are set directly in API version 2, brackets not required
+        p300_pipette.transfer(SOC_VOL, soc, transformation_cols,
+                              new_tip='always', mix_after=SOC_MIX_SETTINGS)
+        p300_pipette.flow_rate.aspirate = P300_DEFAULT_ASPIRATION_RATE
+        # old code:
+            # p300_pipette.set_flow_rate(aspirate=P300_DEFAULT_ASPIRATION_RATE)
+            # API version 2 labware use .columns attribute, not .cols
+
+        # Incubate for 1 hour at 37 °C
+        tempdeck.set_temperature(TEMP)
+        # removed: tempdeck.wait_for_temp()
+            # API version2 automatically pauses execution until the set temperature is reached
+            # thus it no longer uses .wait_for_temp()
+
+        protocol.delay(minutes=OUTGROWTH_TIME)
+        # old code:
+            # p300_pipette.delay(minutes=OUTGROWTH_TIME)
+        # API version 2 no longer has .delay() for pipettes, it uses protocol.delay() to pause the entire protocol
+
+        tempdeck.deactivate()
+
+
+
+
+
+    def spotting_cols(spotting_tuples):
+        """
+        Evaluates spotting_tuples and returns unique cols (str) associated with each spotting_tuple's source wells.
+
+        Args:
+        spotting_tuples (list): Sets of spotting reactions are given in the form: ((source wells), (target wells), (spotting volumes)).
+
+        """
+        cols_list = []
+        for spotting_tuple in spotting_tuples:
+            source_wells_cols = [source_well[1:] for source_well in spotting_tuple[0]]
+            unique_cols = [col for i, col in enumerate(source_wells_cols) if source_wells_cols.index(col) == i]
+            cols_list.append(unique_cols)
+        return cols_list
+
+
+    def spot_transformations(
+            spotting_tuples,
+            dead_vol=1,
+            spotting_dispense_rate=0.025,
+            stabbing_depth=10,
+            max_spot_vol=5):
+        """
+        Spots transformation reactions.
+
+        Args:
+        spotting_tuples (list): Sets of spotting reactions are given in the form: ((source wells), (target wells), (spotting volumes)).
+        dead_vol (float): Dead volume aspirated during spotting.
+        spotting_dispense_rate (float): Rate p20_pipette dispenses at during spotting.
+        stabbing_depth (float): Depth p20_pipette moves into agar during spotting.
+        max_spot_vol (float): Maximum volume that is spotted per spot reaction.
+
+        """
+
+        def spot(
+                source,
+                target,
+                spot_vol):
+            """
+            Spots an individual reaction using the p20 pipette.
+
+            Args:
+            source (str): Well containing the transformation reaction to be spotted.
+            target (str): Well transformation reaction is to be spotted to.
+            spot_vol (float): Volume of transformation reaction to be spotted (uL).
+
+            """
+
+            # Constants
+            DEFAULT_HEAD_SPEED = {'x': 400, 'y': 400,'z': 125, 'a': 125}
+            SPOT_HEAD_SPEED = {'x': 400, 'y': 400, 'z': 125,'a': 125 // 4}
+            DISPENSING_HEIGHT = 5
+            SAFE_HEIGHT = 7  # height avoids collision with agar tray.
+
+            # Spot
+            p20_pipette.pick_up_tip()
+            p20_pipette.aspirate(spot_vol + dead_vol, source[0])
+            # old code:
+                # p20_pipette.aspirate(spot_vol + dead_vol, source)
+                # returned type error because 'source' was a list containing one item (the well location)
+                # source[0] takes the location out of the list
+
+            p20_pipette.move_to(target[0].top(SAFE_HEIGHT))
+            p20_pipette.move_to(target[0].top(DISPENSING_HEIGHT))
+            # old code:
+                # p20_pipette.move_to(target.top(SAFE_HEIGHT))
+                # p20_pipette.move_to(target.top(DISPENSING_HEIGHT))
+                # returned attribute error because 'target' was a list containing one item (the well location)
+                # target[0] takes the location out of the list
+
+            p20_pipette.dispense(volume=spot_vol, rate=spotting_dispense_rate)
+
+            protocol.max_speeds.update(SPOT_HEAD_SPEED)
+            # old code:
+                # robot.head_speed(combined_speed=max(SPOT_HEAD_SPEED.values()), **SPOT_HEAD_SPEED)
+                # robot.head_speed not used in API version 2
+                # replaced with protocol.max_speeds
+            # new code no longer uses the lower value between combined speed or specified speed
+                # just uses each axis' specified speed directly
+            p20_pipette.move_to(target[0].top(-1 * stabbing_depth))
+            # old code:
+                # p20_pipette.move_to(target.top(-1*stabbing_depth))
+                # returns attribute error because 'target' was a list containing one item (the well location)
+            protocol.max_speeds.update(DEFAULT_HEAD_SPEED)
+            # old code:
+                # robot.head_speed(combined_speed=max(DEFAULT_HEAD_SPEED.values()), **DEFAULT_HEAD_SPEED)
+                # robot.head_speed not used in API version 2
+                # replaced with protocol.max_speeds
+            # new code no longer uses the lower value between combined speed or specified speed
+                # just uses each axis' specified speed directly
+
+            #Make sure that the transformed cells drops on the agar plate
+
+            p20_pipette.blow_out()
+
+            protocol.delay(seconds=10)
+
+            p20_pipette.blow_out()
+
+            protocol.delay(seconds=10)
+
+            p20_pipette.blow_out()
+
+            protocol.delay(seconds=10)
+
+            p20_pipette.blow_out()
+
+
+
+            p20_pipette.move_to(target[0].top(SAFE_HEIGHT))
+            # old code:
+                # p20_pipette.move_to(target[0].top(SAFE_HEIGHT))
+                # returns attribute error because 'target' was a list containing one item (the well location)
+
+
+            # the code below makes sure that the transformend cells are efficiently reaching to the agar surface
+
+
+
+            # Dispose of dead volume and tip
+            p20_pipette.dispense(dead_vol, spotting_waste[0])
+            # old code:
+                # p20_pipette.dispense(dead_vol, spotting_waste)
+                # returns type error because 'target' was a list containing one item (the well location)
+
+            p20_pipette.blow_out()
+                # the simple .blow_out command blows out at current position (spotting waste) by defualt
+                # unlike blowout=true in complex commands, which by default will blow out in waste
+
+            p20_pipette.drop_tip()
+
+        def spot_tuple(spotting_tuple):
+            """
+            Spots all reactions defined by the spotting tuple. Requires the function spot.
+
+            Args:
+            spotting_tuple (tuple): Spotting reactions given in the form: (source wells), (target wells), (spotting volumes).
+            Each unique source well is resuspended once prior to spotting.
+
+            """
+            source_wells = spotting_tuple[0]
+            target_wells = spotting_tuple[1]
+            spot_vols = list(spotting_tuple[2])
+            while max(spot_vols) > 0:
+                for index, spot_vol in enumerate(spot_vols):
+                    if spot_vol == 0:
+                        pass
+                    else:
+                        vol = spot_vol if spot_vol <= max_spot_vol else max_spot_vol
+                        spot(source = transformation_plate.wells(source_wells[index]), target = agar_plate.wells(target_wells[index]), spot_vol = vol)
+                        spot_vols[index] = spot_vols[index] - vol
+
+        # Constants
+        TRANSFORMATION_MIX_SETTINGS = [4, 50]
+
+        # Spot transformation reactions
+            # Each unique transformation well is resuspended once prior to spotting.
+
+        for spotting_tuple in spotting_tuples:
+            source_wells_cols = [source_well[1:] for source_well in spotting_tuple[0]]
+            unique_cols = [col for i, col in enumerate(source_wells_cols) if source_wells_cols.index(col) == i]
+            for col in unique_cols:
+                p300_pipette.pick_up_tip()
+                p300_pipette.mix(TRANSFORMATION_MIX_SETTINGS[0], TRANSFORMATION_MIX_SETTINGS[1],transformation_plate.columns_by_name()[col][0])
+                # old code:
+                    # p300_pipette.mix(TRANSFORMATION_MIX_SETTINGS[0], TRANSFORMATION_MIX_SETTINGS[1],transformation_plate.cols(col))
+                    # .columns aka .cols doesn't take lists anymore
+                        # replaced with .columns_by_name
+                    # .mix only takes one location, not several locations
+                        # added [0] to specify only the wells in row A
+                        # is identical for the protocol, as this is using a multi-channel pipette
+                p300_pipette.drop_tip()
+            spot_tuple(spotting_tuple)
+
+
+    # Tiprack slots
+
+    p20_p300_tiprack_slots = tiprack_slots(spotting_tuples)
+    p20_slots = CANDIDATE_p20_SLOTS[:p20_p300_tiprack_slots[0]]
+    p300_slots = CANDIDATE_P300_SLOTS[:p20_p300_tiprack_slots[1]]
+
+    # Define labware
+    p20_tipracks = [protocol.load_labware(P20_TIPRACK_TYPE, slot) for slot in p20_slots]
+        # changed to protocol.load_labware for API version 2
+    p300_tipracks = [protocol.load_labware(P300_TIPRACK_TYPE, slot) for slot in p300_slots]
+        # changed to protocol.load_labware for API version 2
+    p20_pipette = protocol.load_instrument(__LABWARES['p20_single']['id'], P20_MOUNT, tip_racks=p20_tipracks)
+        # changed to protocol.load_instrument for API version 2
+    p300_pipette = protocol.load_instrument(__LABWARES['p300_multi']['id'], P300_MOUNT, tip_racks=p300_tipracks)
+        # changed to protocol.load_instrument for API version 2
+
+    assembly_plate = protocol.load_labware(ASSEMBLY_PLATE_TYPE, ASSEMBLY_PLATE_SLOT)
+        # changed to protocol.load_labware for API version 2
+    tempdeck = protocol.load_module('tempdeck', TEMPDECK_SLOT)
+    transformation_plate = tempdeck.load_labware(TRANSFORMATION_PLATE_TYPE, TEMPDECK_SLOT)
+        # changed to protocol.load_labware for API version 2
+        # removed share=True, not required in API version 2
+        # removed TEMPDECK_SLOT as it is loaded directly onto temperature module
+    soc_plate = protocol.load_labware(SOC_PLATE_TYPE, SOC_PLATE_SLOT)
+        # changed to protocol.load_labware for API version 2
+    tube_rack = protocol.load_labware(TUBE_RACK_TYPE, TUBE_RACK_SLOT)
+        # changed to protocol.load_labware for API version 2
+    spotting_waste = tube_rack.wells(SPOTTING_WASTE_WELL)
+    agar_plate = protocol.load_labware(AGAR_PLATE_TYPE, AGAR_PLATE_SLOT)
+        # changed to protocol.load_labware for API version 2
+
+
+    ### Run protocol
+
+
+
+
+
+    # Run functions
+    transformation_setup(generate_transformation_wells(spotting_tuples))
+    heat_shock()
+    phase_switch()
+    spotting_tuples_cols = [col for cols in spotting_cols(spotting_tuples) for col in cols]
+    unique_cols = [col for i, col in enumerate(spotting_tuples_cols) if spotting_tuples_cols.index(col) == i]
+    outgrowth(cols=unique_cols, soc_well=soc_well)
+    spot_transformations(spotting_tuples)
+    print(unique_cols)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/metainformation/dataset_4472_clip_run_info.csv	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,28 @@
+MASTER_MIX
+Component,Volume (uL)
+"Promega T4 DNA Ligase buffer, 10X",45.0
+Water,232.5
+NEB BsaI-HFv2,15.0
+Promega T4 DNA Ligase,7.5
+
+SOURCE_PLATES
+Deck position,Source plate,Path
+2,dataset_4475.dat,/galaxy/database/files/004/dataset_4475.dat
+5,dataset_4476.dat,/galaxy/database/files/004/dataset_4476.dat
+
+CLIP_REACTIONS
+prefixes,parts,suffixes,number,mag_well
+LMS-P,BASIC_SEVA_37_CmR-p15A.1,LMP-S,1,"('A7',)"
+LMP-P,PJ23104_BASIC,U1-S,1,"('B7',)"
+U1-RBS2-P,D5AP78,U3-S,1,"('C7',)"
+U3-RBS3-P,Q1XBU4,U2-S,1,"('D7',)"
+U2-RBS1-P,O66129,LMS-S,1,"('E7',)"
+LMP-P,PJ23101_BASIC,U1-S,1,"('F7',)"
+U1-RBS1-P,D2WKD9,U2-S,1,"('G7',)"
+U2-RBS3-P,O48935,U3-S,1,"('H7',)"
+U3-RBS1-P,O66952,LMS-S,1,"('A8',)"
+LMP-P,PJ23104_BASIC,U2-S,1,"('B8',)"
+U2-RBS2-P,P48537,U3-S,1,"('C8',)"
+U3-RBS2-P,O07333,U1-S,1,"('D8',)"
+U1-RBS2-P,Q9C446,LMS-S,1,"('E8',)"
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/metainformation/dataset_4472_final_assembly_run_info.csv	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,3 @@
+A1,"['A7', 'B7', 'C7', 'D7', 'E7']"
+B1,"['A7', 'F7', 'G7', 'H7', 'A8']"
+C1,"['A7', 'B8', 'C8', 'D8', 'E8']"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/dnabot_scripts/output/metainformation/dataset_4472_wells.txt	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,2 @@
+Magbead ethanol well: A11
+SOC column: 1
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/linker_parts_coords.csv	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,83 @@
+Part/linker,Well,Part concentration (ng/uL)
+L1-S,A1,
+L1-P,B1,
+L2-S,A2,
+L2-P,B2,
+L3-S,A3,
+L3-P,B3,
+L4-S,A4,
+L4-P,B4,
+L5-S,A5,
+L5-P,B5,
+L6-S,A6,
+L6-P,B6,
+LMP-S,A7,
+LMP-P,B7,
+LMS-S,A8,
+LMS-P,B8,
+U1-S,C1,
+U2-S,C2,
+U3-S,C3,
+U1-RBS1-P,C4,
+U1-RBS2-P,C5,
+U1-RBS3-P,C6,
+U1-A01-P,D1,
+U1-A02-P,D2,
+U1-A03-P,D3,
+U1-A04-P,D4,
+U1-A05-P,D5,
+U1-A06-P,D6,
+U1-A07-P,D7,
+U1-A08-P,D8,
+U1-A09-P,D9,
+U1-A10-P,D10,
+U1-A11-P,D11
+U1-A12-P,D12,
+U2-RBS1-P,C7,
+U2-RBS2-P,C8,
+U2-RBS3-P,C9,
+U2-A01-P,E1,
+U2-A02-P,E2,
+U2-A03-P,E3,
+U2-A04-P,E4,
+U2-A05-P,E5,
+U2-A06-P,E6,
+U2-A07-P,E7,
+U2-A08-P,E8,
+U2-A09-P,E9,
+U2-A10-P,E10,
+U2-A11-P,E11
+U2-A12-P,E12,
+U3-RBS1-P,C10,
+U3-RBS2-P,C11,
+U3-RBS3-P,C12,
+U3-A01-P,F1,
+U3-A02-P,F2,
+U3-A03-P,F3,
+U3-A04-P,F4,
+U3-A05-P,F5,
+U3-A06-P,F6,
+U3-A07-P,F7,
+U3-A08-P,F8,
+U3-A09-P,F9,
+U3-A10-P,F10,
+U3-A11-P,F11
+U3-A12-P,F12,
+U1-AM12-P,A10,
+U1-AM24-P,B10,
+U2-AM12-P,A11,
+U2-AM24-P,B11,
+U3-AM12-P,A12,
+U3-AM24-P,B12,
+LF1-S,A9,
+LF1-P,B9,
+LF2-S,G1,
+LF2-P,H1,
+LF3-S,G2,
+LF3-P,H2,
+LF4-S,G3,
+LF4-P,H3,
+LF5-S,G4,
+LF5-P,H4,
+LF6-S,G5,
+LF6-P,H5,
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/user_parts_coords.csv	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,13 @@
+Part/linker,Well,Part concentration (ng/uL)
+BASIC_SEVA_37_CmR-p15A.1,A1,
+D2WKD9,A2,
+D5AP78,A3,
+O07333,A4,
+O48935,A5,
+O66129,A6,
+O66952,A7,
+P48537,A8,
+PJ23101_BASIC,A9,
+PJ23104_BASIC,A10,
+Q1XBU4,A11,
+Q9C446,A12,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wrap.xml	Tue Dec 14 14:46:38 2021 +0000
@@ -0,0 +1,126 @@
+<tool id="dnabot" name="DNA-Bot" version="3.0.0">
+    <description>DNA assembly using BASIC on OpenTrons</description>
+    <requirements>
+        <requirement type="package" version="3.0.0">dnabot</requirement>
+     </requirements>
+    <command detect_errors="exit_code"><![CDATA[
+        python -m dnabot.dnabot_app
+        #if $adv.default_settings_file
+            --default_settings_file '$adv.default_settings_file'
+        #end if
+        nogui
+        --construct_path '$construct_file'
+        #set files = '" "'.join([str($file) for $file in $plate_files])
+        --source_paths "${files}"
+        --etoh_well '$adv.etoh_well'
+        --soc_column '$adv.soc_column'
+        --output_dir 'output'
+        && tar -cvf '$dnabot_scripts' 'output'
+    ]]></command>
+    <inputs>
+        <param name="construct_file" type="data" format="csv" label="Source Construct" />
+        <param name="plate_files" type="data" format="csv" multiple="true" label="Plate files" />
+        <section name="adv" title="Advanced Options" expanded="false">
+            <param name="default_settings_file" type="data" format="yaml" optional="true" label="Yaml file providing labware IDs and parameter to be used" />
+            <param name="etoh_well" type="select" label="Well coordinate for Ethanol">
+                <option value="A2" >A2</option>
+                <option value="A3" >A3</option>
+                <option value="A4" >A4</option>
+                <option value="A5" >A5</option>
+                <option value="A6" >A6</option>
+                <option value="A7" >A7</option>
+                <option value="A8" >A8</option>
+                <option value="A9" >A9</option>
+                <option value="A10" >A10</option>
+                <option value="A11" selected="true">A11</option>
+            </param>
+            <param name="soc_column" type="select" label="Column coordinate for SOC">
+                <option value="1" selected="true">1</option>
+                <option value="2" >2</option>
+                <option value="3" >3</option>
+                <option value="4" >4</option>
+                <option value="5" >5</option>
+                <option value="6" >6</option>
+                <option value="7" >7</option>
+                <option value="8" >8</option>
+                <option value="9" >9</option>
+                <option value="10" >10</option>
+                <option value="11" >11</option>
+                <option value="12" >12</option>
+            </param>
+        </section>
+    </inputs>
+    <outputs>
+        <data name="dnabot_scripts" format="tar" label="${tool.name} scripts" />
+    </outputs>
+    <tests>
+        <test>
+        <!-- test 1: check if identical outputs are produced with compress option -->
+            <param name="construct_file" value="constructs.csv" />
+            <param name="plate_files" value="user_parts_coords.csv,linker_parts_coords.csv"/>
+            <output name="dnabot_scripts" file="dnabot_scripts.tar" compare="diff" decompress="True" lines_diff="3"/>
+        </test>
+    </tests>
+    <help><![CDATA[
+DNA-Bot
+============
+
+DNA assembly using BASIC on OpenTrons
+
+Input
+-----
+
+* **default_settings_file**: (string) file providing labware IDs and parameter to be used. Default: dnabot/default_settings.yaml.
+* **construct_path**: (string) Construct CSV file.
+* **source_paths**: (string) Source CSV files.
+* **etoh_well**: (string) Well coordinate for Ethanol. Default: A11.
+* **soc_column**: (integer) Column coordinate for SOC. Default: 1.
+* **template_dir**: (string) Template directory. Default: "template_ot2_scripts" located next to the present script.
+
+Ouput
+-----
+
+* **output_dir**: (string) Output directory. Default: same directory than the one containing the "construct_path" file.
+
+Version
+-------
+
+3.0.0
+
+Authors
+-------
+
+* **Matthew C Haines**
+* Thomas Duigou
+
+License
+-------
+
+`MIT <https://github.com/brsynth/DNA-BOT/blob/DNA-BOT-APIv2/LICENSE>`_
+
+
+Acknowledgments
+---------------
+
+* Marko Storch
+* Geoff Baldwin
+    ]]></help>
+    <citations>
+        <citation type="bibtex">
+            @article{10.1093/synbio/ysaa010,
+                author = {Storch, Marko and Haines, Matthew C and Baldwin, Geoff S},
+                title = {DNA-BOT: a low-cost, automated DNA assembly platform for synthetic biology},
+                journal = {Synthetic Biology},
+                volume = {5},
+                number = {1},
+                year = {2020},
+                month = {07},
+                issn = {2397-7000},
+                doi = {10.1093/synbio/ysaa010},
+                url = {https://doi.org/10.1093/synbio/ysaa010},
+                note = {ysaa010},
+                eprint = {https://academic.oup.com/synbio/article-pdf/5/1/ysaa010/33722340/ysaa010.pdf},
+            }
+        </citation>
+    </citations>
+</tool>
\ No newline at end of file