diff --git a/Makefile b/Makefile index d3ddd15f359..7a5bfc7a7e9 100755 --- a/Makefile +++ b/Makefile @@ -152,6 +152,10 @@ push: sleep 1 $(MAKE) -C $(UPDATE_SERVER_DIR) push +.PHONY: push-folder +PUSH_HELPER := abr-testing/abr_testing/tools/make_push.py +push-folder: + $(OT_PYTHON) $(PUSH_HELPER) .PHONY: push-ot3 push-ot3: diff --git a/abr-testing/abr_testing/data_collection/abr_calibration_logs.py b/abr-testing/abr_testing/data_collection/abr_calibration_logs.py index 75b73b8f16b..46cc409e53d 100644 --- a/abr-testing/abr_testing/data_collection/abr_calibration_logs.py +++ b/abr-testing/abr_testing/data_collection/abr_calibration_logs.py @@ -286,6 +286,7 @@ def run( ip_json_file = os.path.join(storage_directory, "IPs.json") try: ip_file = json.load(open(ip_json_file)) + robot_dict = ip_file.get("ip_address_list") except FileNotFoundError: print(f"Add .json file with robot IPs to: {storage_directory}.") sys.exit() @@ -294,7 +295,7 @@ def run( ip_or_all = input("IP Address or ALL: ") calibration_data = [] if ip_or_all.upper() == "ALL": - ip_address_list = ip_file["ip_address_list"] + ip_address_list = list(robot_dict.keys()) for ip in ip_address_list: saved_file_path, calibration = read_robot_logs.get_calibration_offsets( ip, storage_directory diff --git a/abr-testing/abr_testing/data_collection/abr_google_drive.py b/abr-testing/abr_testing/data_collection/abr_google_drive.py index 6552534c4ae..8f82567a7d1 100644 --- a/abr-testing/abr_testing/data_collection/abr_google_drive.py +++ b/abr-testing/abr_testing/data_collection/abr_google_drive.py @@ -44,6 +44,7 @@ def create_data_dictionary( headers: List[str] = [] headers_lpc: List[str] = [] list_of_heights: List[List[Any]] = [[], [], [], [], [], [], [], []] + hellma_plate_orientation = False # default hellma plate is not rotated. for filename in os.listdir(storage_directory): file_path = os.path.join(storage_directory, filename) if file_path.endswith(".json"): @@ -67,6 +68,10 @@ def create_data_dictionary( if run_id in runs_to_save: print(f"started reading run {run_id}.") robot = file_results.get("robot_name") + parameters = file_results.get("runTimeParameters", "") + for parameter in parameters: + if parameter["displayName"] == "Hellma Plate Orientation": + hellma_plate_orientation = bool(parameter["value"]) protocol_name = file_results["protocol"]["metadata"].get("protocolName", "") software_version = file_results.get("API_Version", "") left_pipette = file_results.get("left", "") @@ -123,7 +128,7 @@ def create_data_dictionary( file_results, labware_name="opentrons_tough_pcr_auto_sealing_lid" ) plate_reader_dict = read_robot_logs.plate_reader_commands( - file_results, hellma_plate_standards + file_results, hellma_plate_standards, hellma_plate_orientation ) list_of_heights = read_robot_logs.liquid_height_commands( file_results, list_of_heights diff --git a/abr-testing/abr_testing/data_collection/get_run_logs.py b/abr-testing/abr_testing/data_collection/get_run_logs.py index 964a8a06e18..fe89f9f1543 100644 --- a/abr-testing/abr_testing/data_collection/get_run_logs.py +++ b/abr-testing/abr_testing/data_collection/get_run_logs.py @@ -104,10 +104,11 @@ def get_all_run_logs( ip_json_file = os.path.join(storage_directory, "IPs.json") try: ip_file = json.load(open(ip_json_file)) + robot_dict = ip_file.get("ip_address_list") except FileNotFoundError: print(f"Add .json file with robot IPs to: {storage_directory}.") sys.exit() - ip_address_list = ip_file["ip_address_list"] + ip_address_list = list(robot_dict.keys()) runs_from_storage = read_robot_logs.get_run_ids_from_google_drive(google_drive) for ip in ip_address_list: runs = get_run_ids_from_robot(ip) diff --git a/abr-testing/abr_testing/data_collection/read_robot_logs.py b/abr-testing/abr_testing/data_collection/read_robot_logs.py index 40712118fe5..7bc83e0a54b 100644 --- a/abr-testing/abr_testing/data_collection/read_robot_logs.py +++ b/abr-testing/abr_testing/data_collection/read_robot_logs.py @@ -250,7 +250,9 @@ def liquid_height_commands( def plate_reader_commands( - file_results: Dict[str, Any], hellma_plate_standards: List[Dict[str, Any]] + file_results: Dict[str, Any], + hellma_plate_standards: List[Dict[str, Any]], + orientation: bool, ) -> Dict[str, object]: """Plate Reader Command Counts.""" commandData = file_results.get("commands", "") @@ -279,38 +281,46 @@ def plate_reader_commands( read = "yes" elif read == "yes" and commandType == "comment": result = command["params"].get("message", "") - formatted_result = result.split("result: ")[1] - result_dict = eval(formatted_result) - result_dict_keys = list(result_dict.keys()) - if len(result_dict_keys) > 1: - read_type = "multi" - else: - read_type = "single" - for wavelength in result_dict_keys: - one_wavelength_dict = result_dict.get(wavelength) - result_ndarray = plate_reader.convert_read_dictionary_to_array( - one_wavelength_dict - ) - for item in hellma_plate_standards: - wavelength_of_interest = item["wavelength"] - if str(wavelength) == str(wavelength_of_interest): - error_cells = plate_reader.check_byonoy_data_accuracy( - result_ndarray, item, False + if "result:" in result: + plate_name = result.split("result:")[0] + formatted_result = result.split("result: ")[1] + print(formatted_result) + result_dict = eval(formatted_result) + result_dict_keys = list(result_dict.keys()) + if len(result_dict_keys) > 1: + read_type = "multi" + else: + read_type = "single" + if "hellma_plate" in plate_name: + for wavelength in result_dict_keys: + one_wavelength_dict = result_dict.get(wavelength) + result_ndarray = plate_reader.convert_read_dictionary_to_array( + one_wavelength_dict ) - if len(error_cells[0]) > 0: - percent = (96 - len(error_cells)) / 96 * 100 - for cell in error_cells: - print( - "FAIL: Cell " + str(cell) + " out of accuracy spec." + for item in hellma_plate_standards: + wavelength_of_interest = item["wavelength"] + if str(wavelength) == str(wavelength_of_interest): + error_cells = plate_reader.check_byonoy_data_accuracy( + result_ndarray, item, orientation ) - else: - percent = 100 - print( - f"PASS: {wavelength_of_interest} meet accuracy specification" - ) - final_result[read_type, wavelength, read_num] = percent - read_num += 1 - read = "no" + if len(error_cells[0]) > 0: + percent = (96 - len(error_cells)) / 96 * 100 + for cell in error_cells: + print( + "FAIL: Cell " + + str(cell) + + " out of accuracy spec." + ) + else: + percent = 100 + print( + f"PASS: {wavelength_of_interest} meet accuracy spec." + ) + final_result[read_type, wavelength, read_num] = percent + read_num += 1 + else: + final_result = result_dict + read = "no" plate_dict = { "Plate Reader # of Reads": read_count, "Plate Reader Avg Read Time (sec)": avg_read_time, diff --git a/abr-testing/abr_testing/protocol_simulation/abr_sim_check.py b/abr-testing/abr_testing/protocol_simulation/abr_sim_check.py index f55c9ebb51f..76852f70b9c 100644 --- a/abr-testing/abr_testing/protocol_simulation/abr_sim_check.py +++ b/abr-testing/abr_testing/protocol_simulation/abr_sim_check.py @@ -46,7 +46,7 @@ def get_files() -> Tuple[Dict[str, Dict[str, Union[str, Path]]], List[Path]]: labware_defs = [] for root, directories, _ in os.walk(root_dir): for directory in directories: - if directory == "active_protocols": + if directory not in exclude: active_dir = os.path.join(root, directory) for file in os.listdir( active_dir @@ -100,7 +100,6 @@ def get_files() -> Tuple[Dict[str, Dict[str, Union[str, Path]]], List[Path]]: exclude = [ "__init__.py", "helpers.py", - "shared_vars_and_funcs.py", ] print("Simulating Protocols") file_dict, labware_defs = get_files() diff --git a/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py b/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py index 57695f03557..10c7ea12782 100644 --- a/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py +++ b/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py @@ -225,7 +225,11 @@ def parse_results_volume( else: print(f"Expected JSON object (dict) but got {type(json_data).__name__}.") commands = {} - + hellma_plate_orientation = False + parameters = json_data.get("runTimeParameters", "") + for parameter in parameters: + if parameter["displayName"] == "Hellma Plate Orientation": + hellma_plate_orientation = bool(parameter["value"]) start_time = datetime.fromisoformat(commands[0]["createdAt"]) end_time = datetime.fromisoformat(commands[len(commands) - 1]["completedAt"]) header = ["", "Protocol Name", "Date", "Time"] @@ -283,7 +287,7 @@ def parse_results_volume( temp_module_dict = read_robot_logs.temperature_module_commands(json_data) thermo_cycler_dict = read_robot_logs.thermocycler_commands(json_data) plate_reader_dict = read_robot_logs.plate_reader_commands( - json_data, hellma_plate_standards + json_data, hellma_plate_standards, hellma_plate_orientation ) instrument_dict = read_robot_logs.instrument_commands( json_data, labware_name=None @@ -499,12 +503,12 @@ def check_params(protocol_path: str) -> str: def get_extra_files(protocol_file_path: str) -> tuple[str, List[Path]]: """Get supporting files for protocol simulation if needed.""" params = check_params(protocol_file_path) - needs_files = input("Does your protocol utilize custom labware? (y/n): ") + needs_files = input("Does your protocol utilize custom labware? (Y/N): ") labware_files = [] - if needs_files == "y": + if needs_files == "Y": num_labware = input("How many custom labware?: ") for labware_num in range(int(num_labware)): - path = input("Enter custom labware definition: ") + path = input("Enter custom labware definition path: ") labware_files.append(Path(path)) return (params, labware_files) diff --git a/abr-testing/abr_testing/protocols/active_protocols/3_OT3 ABR Normalize with Tubes.py b/abr-testing/abr_testing/protocols/active_protocols/3_OT3 ABR Normalize with Tubes.py deleted file mode 100644 index 50fb82e94d5..00000000000 --- a/abr-testing/abr_testing/protocols/active_protocols/3_OT3 ABR Normalize with Tubes.py +++ /dev/null @@ -1,343 +0,0 @@ -"""FLEX Normalize with Tubes.""" -from opentrons.protocol_api import ProtocolContext, ParameterContext, Well -from abr_testing.protocols import helpers -from typing import List - -metadata = { - "protocolName": "Flex Normalize with Tubes", - "author": "Opentrons ", - "source": "Protocol Library", -} - -requirements = {"robotType": "Flex", "apiLevel": "2.21"} - -# SCRIPT SETTINGS -ABR_TEST = True -if ABR_TEST: - DRYRUN = True # True = skip incubation times, shorten mix, for testing purposes - TIP_TRASH = ( - False # True = Used tips go in Trash, False = Used tips go back into rack - ) -else: - DRYRUN = False # True = skip incubation times, shorten mix, for testing purposes - TIP_TRASH = True - - -def add_parameters(parameters: ParameterContext) -> None: - """Parameters.""" - helpers.create_csv_parameter(parameters) - helpers.create_dot_bottom_parameter(parameters) - helpers.create_two_pipette_mount_parameters(parameters) - - -def run(ctx: ProtocolContext) -> None: - """Protocol.""" - mount_pos_50ul = ctx.params.pipette_mount_1 # type: ignore[attr-defined] - mount_pos_1000ul = ctx.params.pipette_mount_2 # type: ignore[attr-defined] - dot_bottom = ctx.params.dot_bottom # type: ignore[attr-defined] - parsed_csv = ctx.params.parameters_csv.parse_as_csv() # type: ignore[attr-defined] - if DRYRUN: - ctx.comment("THIS IS A DRY RUN") - else: - ctx.comment("THIS IS A REACTION RUN") - - # labware - tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "1") - tiprack_200_1 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "4") - reagent_tube = ctx.load_labware( - "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", "5", "Reagent Tube" - ) - sample_plate = ctx.load_labware( - "armadillo_96_wellplate_200ul_pcr_full_skirt", "2", "Sample Plate" - ) - - # reagent - RSB = reagent_tube.wells()[0] - - # pipette - p1000 = ctx.load_instrument( - "flex_1channel_1000", mount_pos_1000ul, tip_racks=[tiprack_200_1] - ) - p50 = ctx.load_instrument( - "flex_1channel_50", mount_pos_50ul, tip_racks=[tiprack_50_1] - ) - - wells_with_liquids: List[Well] = [RSB] - helpers.load_wells_with_water(ctx, wells_with_liquids, [4000.0]) - helpers.find_liquid_height_of_all_wells(ctx, p50, wells_with_liquids) - MaxTubeVol = 200 - RSBVol = 0.0 - - data = parsed_csv - current = 1 - while current < len(data): - - CurrentWell = str(data[current][1]) - if float(data[current][2]) > 0: - InitialVol = float(data[current][2]) - else: - InitialVol = 0 - if float(data[current][3]) > 0: - InitialConc = float(data[current][3]) - else: - InitialConc = 0 - if float(data[current][4]) > 0: - TargetConc = float(data[current][4]) - else: - TargetConc = 0 - TotalDNA = float(InitialConc * InitialVol) - if TargetConc > 0: - TargetVol = float(TotalDNA / TargetConc) - else: - TargetVol = InitialVol - if TargetVol > InitialVol: - DilutionVol = float(TargetVol - InitialVol) - else: - DilutionVol = 0 - FinalVol = float(DilutionVol + InitialVol) - if TotalDNA > 0 and FinalVol > 0: - FinalConc = float(TotalDNA / FinalVol) - else: - FinalConc = 0 - - if DilutionVol <= 1: - ctx.comment("Sample " + CurrentWell + ": Conc. Too Low, Will Skip") - elif DilutionVol > MaxTubeVol - InitialVol: - DilutionVol = MaxTubeVol - InitialVol - ctx.comment( - "Sample " - + CurrentWell - + ": Conc. Too High, Will add, " - + str(DilutionVol) - + "ul, Max = " - + str(MaxTubeVol) - + "ul" - ) - RSBVol += MaxTubeVol - InitialVol - else: - if DilutionVol <= 20: - ctx.comment( - "Sample " - + CurrentWell - + ": Using p50, will add " - + str(round(DilutionVol, 1)) - ) - elif DilutionVol > 20: - ctx.comment( - "Sample " - + CurrentWell - + ": Using p1000, will add " - + str(round(DilutionVol, 1)) - ) - RSBVol += DilutionVol - current += 1 - - if RSBVol >= 14000: - ctx.pause("Caution, more than 15ml Required") - else: - ctx.comment("RSB Minimum: " + str(round(RSBVol / 1000, 1) + 1) + "ml") - - PiR2 = 176.71 - InitialRSBVol = RSBVol - RSBHeight = (InitialRSBVol / PiR2) + 17.5 - - ctx.pause("Proceed") - ctx.comment("==============================================") - ctx.comment("Normalizing Samples") - ctx.comment("==============================================") - - current = 1 - while current < len(data): - - CurrentWell = str(data[current][1]) - if float(data[current][2]) > 0: - InitialVol = float(data[current][2]) - else: - InitialVol = 0 - if float(data[current][3]) > 0: - InitialConc = float(data[current][3]) - else: - InitialConc = 0 - if float(data[current][4]) > 0: - TargetConc = float(data[current][4]) - else: - TargetConc = 0 - TotalDNA = float(InitialConc * InitialVol) - if TargetConc > 0: - TargetVol = float(TotalDNA / TargetConc) - else: - TargetVol = InitialVol - if TargetVol > InitialVol: - DilutionVol = float(TargetVol - InitialVol) - else: - DilutionVol = 0 - FinalVol = float(DilutionVol + InitialVol) - if TotalDNA > 0 and FinalVol > 0: - FinalConc = float(TotalDNA / FinalVol) - else: - FinalConc = 0 - - ctx.comment("Number " + str(data[current]) + ": Sample " + str(CurrentWell)) - # ctx.comment("Vol Height = "+str(round(RSBHeight,2))) - HeightDrop = DilutionVol / PiR2 - # ctx.comment("Vol Drop = "+str(round(HeightDrop,2))) - - if DilutionVol <= 0: - # If the No Volume - ctx.comment("Conc. Too Low, Skipping") - - elif DilutionVol >= MaxTubeVol - InitialVol: - # If the Required Dilution volume is >= Max Volume - DilutionVol = MaxTubeVol - InitialVol - ctx.comment( - "Conc. Too High, Will add, " - + str(DilutionVol) - + "ul, Max = " - + str(MaxTubeVol) - + "ul" - ) - p1000.pick_up_tip() - p1000.require_liquid_presence(RSB) - p1000.aspirate(DilutionVol, RSB.bottom(RSBHeight - (HeightDrop))) - RSBHeight -= HeightDrop - # ctx.comment("New Vol Height = "+str(round(RSBHeight,2))) - p1000.dispense(DilutionVol, sample_plate.wells_by_name()[CurrentWell]) - wells_with_liquids.append(sample_plate.wells_by_name()[CurrentWell]) - HighVolMix = 10 - for Mix in range(HighVolMix): - p1000.move_to(sample_plate.wells_by_name()[CurrentWell].center()) - p1000.aspirate(100) - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].bottom(0.5) - ) # original = () - p1000.aspirate(100) - p1000.dispense(100) - p1000.move_to(sample_plate.wells_by_name()[CurrentWell].center()) - p1000.dispense(100) - wells_with_liquids.append(sample_plate.wells_by_name()[CurrentWell]) - Mix += 1 - p1000.move_to(sample_plate.wells_by_name()[CurrentWell].top()) - ctx.delay(seconds=3) - p1000.blow_out() - p1000.drop_tip() if DRYRUN is False else p1000.return_tip() - - else: - if DilutionVol <= 20: - # If the Required Dilution volume is <= 20ul - ctx.comment("Using p50 to add " + str(round(DilutionVol, 1))) - p50.pick_up_tip() - if round(float(data[current][3]), 1) <= 20: - p50.require_liquid_presence(RSB) - p50.aspirate(DilutionVol, RSB.bottom(RSBHeight - (HeightDrop))) - RSBHeight -= HeightDrop - else: - p50.require_liquid_presence(RSB) - p50.aspirate(20, RSB.bottom(RSBHeight - (HeightDrop))) - RSBHeight -= HeightDrop - p50.dispense(DilutionVol, sample_plate.wells_by_name()[CurrentWell]) - wells_with_liquids.append(sample_plate.wells_by_name()[CurrentWell]) - p50.move_to( - sample_plate.wells_by_name()[CurrentWell].bottom(z=dot_bottom) - ) # original = () - # Mix volume <=20ul - if DilutionVol + InitialVol <= 20: - p50.mix(10, DilutionVol + InitialVol) - elif DilutionVol + InitialVol > 20: - p50.mix(10, 20) - p50.move_to(sample_plate.wells_by_name()[CurrentWell].top()) - ctx.delay(seconds=3) - p50.blow_out() - p50.drop_tip() if DRYRUN is False else p50.return_tip() - - elif DilutionVol > 20: - # If the required volume is >20 - ctx.comment("Using p1000 to add " + str(round(DilutionVol, 1))) - p1000.pick_up_tip() - p1000.require_liquid_presence(RSB) - p1000.aspirate(DilutionVol, RSB.bottom(RSBHeight - (HeightDrop))) - RSBHeight -= HeightDrop - if DilutionVol + InitialVol >= 120: - HighVolMix = 10 - for Mix in range(HighVolMix): - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].center() - ) - p1000.aspirate(100) - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].bottom( - z=dot_bottom - ) - ) # original = () - p1000.aspirate(DilutionVol + InitialVol - 100) - p1000.dispense(100) - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].center() - ) - p1000.dispense(DilutionVol + InitialVol - 100) - Mix += 1 - wells_with_liquids.append( - sample_plate.wells_by_name()[CurrentWell] - ) - else: - p1000.dispense( - DilutionVol, sample_plate.wells_by_name()[CurrentWell] - ) - p1000.move_to( - sample_plate.wells_by_name()[CurrentWell].bottom(z=dot_bottom) - ) # original = () - p1000.mix(10, DilutionVol + InitialVol) - p1000.move_to(sample_plate.wells_by_name()[CurrentWell].top()) - wells_with_liquids.append(sample_plate.wells_by_name()[CurrentWell]) - ctx.delay(seconds=3) - p1000.blow_out() - p1000.drop_tip() if DRYRUN is False else p1000.return_tip() - current += 1 - - ctx.comment("==============================================") - ctx.comment("Results") - ctx.comment("==============================================") - - current = 1 - while current < len(data): - - CurrentWell = str(data[current][1]) - if float(data[current][2]) > 0: - InitialVol = float(data[current][2]) - else: - InitialVol = 0 - if float(data[current][3]) > 0: - InitialConc = float(data[current][3]) - else: - InitialConc = 0 - if float(data[current][4]) > 0: - TargetConc = float(data[current][4]) - else: - TargetConc = 0 - TotalDNA = float(InitialConc * InitialVol) - if TargetConc > 0: - TargetVol = float(TotalDNA / TargetConc) - else: - TargetVol = InitialVol - if TargetVol > InitialVol: - DilutionVol = float(TargetVol - InitialVol) - else: - DilutionVol = 0 - if DilutionVol > MaxTubeVol - InitialVol: - DilutionVol = MaxTubeVol - InitialVol - FinalVol = float(DilutionVol + InitialVol) - if TotalDNA > 0 and FinalVol > 0: - FinalConc = float(TotalDNA / FinalVol) - else: - FinalConc = 0 - ctx.comment( - "Sample " - + CurrentWell - + ": " - + str(round(FinalVol, 1)) - + " at " - + str(round(FinalConc, 1)) - + "ng/ul" - ) - - current += 1 - helpers.find_liquid_height_of_all_wells(ctx, p50, wells_with_liquids) diff --git a/abr-testing/abr_testing/protocols/active_protocols/3_Tartrazine Protocol.py b/abr-testing/abr_testing/protocols/active_protocols/3_Tartrazine Protocol.py new file mode 100644 index 00000000000..05a6300e053 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/3_Tartrazine Protocol.py @@ -0,0 +1,124 @@ +"""Tartrazine Protocol.""" +from opentrons.protocol_api import ProtocolContext, ParameterContext, Well +from abr_testing.protocols import helpers +from opentrons.protocol_api.module_contexts import ( + AbsorbanceReaderContext, + HeaterShakerContext, +) +from datetime import datetime +from typing import Dict, List +import statistics + +metadata = { + "protocolName": "Tartrazine Protocol", + "author": "Opentrons ", + "source": "Protocol Library", +} + +requirements = {"robotType": "Flex", "apiLevel": "2.21"} + + +def add_parameters(parameters: ParameterContext) -> None: + """Parameters.""" + helpers.create_single_pipette_mount_parameter(parameters) + + +def run(ctx: ProtocolContext) -> None: + """Protocol.""" + mount_pos_50ul = ctx.params.pipette_mount # type: ignore[attr-defined] + # Plate Reader + plate_reader: AbsorbanceReaderContext = ctx.load_module( + helpers.abs_mod_str, "A3" + ) # type: ignore[assignment] + hs: HeaterShakerContext = ctx.load_module(helpers.hs_str, "A1") # type: ignore[assignment] + hs_adapter = hs.load_adapter("opentrons_96_pcr_adapter") + tube_rack = ctx.load_labware( + "opentrons_10_tuberack_nest_4x50ml_6x15ml_conical", "C2", "Reagent Tube" + ) + tartrazine_tube = tube_rack["A3"] + + sample_plate_1 = ctx.load_labware( + "nest_96_wellplate_200ul_flat", "D1", "Sample Plate 1" + ) + sample_plate_2 = ctx.load_labware( + "nest_96_wellplate_200ul_flat", "C1", "Sample Plate 2" + ) + sample_plate_3 = ctx.load_labware( + "nest_96_wellplate_200ul_flat", "B1", "Sample Plate 3" + ) + sample_plate_list = [sample_plate_1, sample_plate_2, sample_plate_3] + tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "D3") + tiprack_50_2 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "C3") + tiprack_50_3 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "B3") + tip_racks = [tiprack_50_1, tiprack_50_2, tiprack_50_3] + + # Pipette + p50 = ctx.load_instrument("flex_1channel_50", mount_pos_50ul, tip_racks=tip_racks) + + # Probe wells + liquid_vols_and_wells: Dict[str, List[Dict[str, Well | List[Well] | float]]] = { + "Tartrazine": [{"well": tartrazine_tube, "volume": 45.0}] + } + helpers.find_liquid_height_of_loaded_liquids(ctx, liquid_vols_and_wells, p50) + + i = 0 + all_percent_error_dict = {} + cv_dict = {} + for sample_plate in sample_plate_list: + deck_locations = ["D1", "C1", "B1"] + for well in sample_plate.wells(): + p50.pick_up_tip() + height = helpers.find_liquid_height(p50, tartrazine_tube) + p50.aspirate(10, tartrazine_tube.bottom(z=height)) + p50.air_gap(5) + p50.dispense(5, well.top()) + p50.dispense(10, well.bottom(z=0.5)) + p50.blow_out() + p50.return_tip() + helpers.move_labware_to_hs(ctx, sample_plate, hs, hs_adapter) + helpers.set_hs_speed(ctx, hs, 1500, 2.0, True) + hs.open_labware_latch() + plate_reader.close_lid() + plate_reader.initialize("single", [450]) + plate_reader.open_lid() + ctx.move_labware(sample_plate, plate_reader, use_gripper=True) + sample_plate_name = "sample plate_" + str(i + 1) + csv_string = sample_plate_name + "_" + str(datetime.now()) + plate_reader.close_lid() + result = plate_reader.read(csv_string) + for wavelength in result: + dict_of_wells = result[wavelength] + readings_and_wells = dict_of_wells.items() + readings = dict_of_wells.values() + avg = statistics.mean(readings) + # Check if every average is within +/- 5% of 2.85 + percent_error_dict = {} + percent_error_sum = 0.0 + for reading in readings_and_wells: + well_name = str(reading[0]) + measurement = reading[1] + percent_error = (measurement - 2.85) / 2.85 * 100 + percent_error_dict[well_name] = percent_error + percent_error_sum += percent_error + avg_percent_error = percent_error_sum / 96.0 + standard_deviation = statistics.stdev(readings) + try: + cv = standard_deviation / avg + except ZeroDivisionError: + cv = 0.0 + cv_percent = cv * 100 + cv_dict[sample_plate_name] = { + "CV": cv_percent, + "Mean": avg, + "SD": standard_deviation, + "Avg Percent Error": avg_percent_error, + } + all_percent_error_dict[sample_plate_name] = percent_error_dict + plate_reader.open_lid() + ctx.move_labware(sample_plate, deck_locations[i], use_gripper=True) + i += 1 + + # Print percent error dictionary + ctx.comment("Percent Error: " + str(all_percent_error_dict)) + # Print cv dictionary + ctx.comment("Plate Reader result: " + str(cv_dict)) diff --git a/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py b/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py index 2e835ac04dd..4894cae41d4 100644 --- a/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py +++ b/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py @@ -19,10 +19,7 @@ } -requirements = { - "robotType": "Flex", - "apiLevel": "2.21", -} +requirements = {"robotType": "Flex", "apiLevel": "2.21"} HELLMA_PLATE_SLOT = "D4" PLATE_READER_SLOT = "C3" @@ -58,14 +55,11 @@ def add_parameters(parameters: ParameterContext) -> None: """Add Parameters.""" helpers.create_hs_speed_parameter(parameters) helpers.create_dot_bottom_parameter(parameters) - parameters.add_str( + parameters.add_bool( variable_name="plate_orientation", display_name="Hellma Plate Orientation", - default="0_deg", - choices=[ - {"display_name": "0 degree Rotation", "value": "0_deg"}, - {"display_name": "180 degree Rotation", "value": "180_deg"}, - ], + default=True, + description="If hellma plate is rotated, set to True.", ) @@ -73,6 +67,7 @@ def plate_reader_actions( protocol: ProtocolContext, plate_reader: AbsorbanceReaderContext, hellma_plate: Labware, + hellma_plate_name: str, ) -> None: """Plate reader single and multi wavelength readings.""" wavelengths = [450, 650] @@ -84,7 +79,7 @@ def plate_reader_actions( protocol.move_labware(hellma_plate, plate_reader, use_gripper=True) plate_reader.close_lid() result = plate_reader.read(str(datetime.now())) - msg = f"result: {result}" + msg = f"{hellma_plate_name} result: {result}" protocol.comment(msg=msg) plate_reader.open_lid() protocol.move_labware(hellma_plate, HELLMA_PLATE_SLOT, use_gripper=True) @@ -95,7 +90,7 @@ def plate_reader_actions( protocol.move_labware(hellma_plate, plate_reader, use_gripper=True) plate_reader.close_lid() result = plate_reader.read(str(datetime.now())) - msg = f"result: {result}" + msg = f"{hellma_plate_name} result: {result}" protocol.comment(msg=msg) plate_reader.open_lid() protocol.move_labware(hellma_plate, HELLMA_PLATE_SLOT, use_gripper=True) @@ -107,6 +102,8 @@ def run(protocol: ProtocolContext) -> None: # LOAD PARAMETERS heater_shaker_speed = protocol.params.heater_shaker_speed # type: ignore[attr-defined] dot_bottom = protocol.params.dot_bottom # type: ignore[attr-defined] + plate_orientation = protocol.params.plate_orientation # type: ignore[attr-defined] + plate_name_str = "hellma_plate_" + str(plate_orientation) global p200_tips global p50_tips # WASTE BIN @@ -182,7 +179,7 @@ def run(protocol: ProtocolContext) -> None: PPC = reagent_plate.wells_by_name()["A6"] EPM = reagent_plate.wells_by_name()["A7"] # Load Liquids - plate_reader_actions(protocol, plate_reader, hellma_plate) + plate_reader_actions(protocol, plate_reader, hellma_plate, plate_name_str) # tip and sample tracking if COLUMNS == 1: @@ -948,4 +945,4 @@ def tipcheck() -> None: p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() p200_tips += 1 tipcheck() - plate_reader_actions(protocol, plate_reader, hellma_plate) + plate_reader_actions(protocol, plate_reader, hellma_plate, plate_name_str) diff --git a/abr-testing/abr_testing/tools/make_push.py b/abr-testing/abr_testing/tools/make_push.py new file mode 100644 index 00000000000..28a69b11103 --- /dev/null +++ b/abr-testing/abr_testing/tools/make_push.py @@ -0,0 +1,95 @@ +"""Push one or more folders to one or more robots.""" +import subprocess +import multiprocessing +import json + +global folders +# Opentrons folders that can be pushed to robot +folders = [ + "abr-testing", + "hardware-testing", + "abr-testing + hardware-testing", + "other", +] + + +def push_subroutine(cmd: str) -> None: + """Pushes specified folder to specified robot.""" + try: + subprocess.run(cmd) + except Exception: + print("failed to push folder") + raise + + +def main(folder_to_push: str, robot_to_push: str) -> int: + """Main process!""" + cmd = "make -C {folder} push-ot3 host={ip}" + robot_ip_path = "" + push_cmd = "" + folder_int = int(folder_to_push) + if folders[folder_int].lower() == "abr-testing + hardware-testing": + if robot_to_push.lower() == "all": + robot_ip_path = input("Path to robot ips: ") + with open(robot_ip_path, "r") as ip_file: + robot_json = json.load(ip_file) + robot_ips_dict = robot_json.get("ip_address_list") + robot_ips = list(robot_ips_dict.keys()) + ip_file.close() + else: + robot_ips = [robot_to_push] + for folder_name in folders[:-2]: + # Push abr-testing and hardware-testing folders to all robots + for robot in robot_ips: + print_proc = multiprocessing.Process( + target=print, args=(f"Pushing {folder_name} to {robot}!\n\n",) + ) + print_proc.start() + print_proc.join() + push_cmd = cmd.format(folder=folder_name, ip=robot) + process = multiprocessing.Process( + target=push_subroutine, args=(push_cmd,) + ) + process.start() + process.join() + print_proc = multiprocessing.Process(target=print, args=("Done!\n\n",)) + print_proc.start() + print_proc.join() + else: + + if folder_int == (len(folders) - 1): + folder_name = input("Which folder? ") + else: + folder_name = folders[folder_int] + if robot_to_push.lower() == "all": + robot_ip_path = input("Path to robot ips: ") + with open(robot_ip_path, "r") as ip_file: + robot_json = json.load(ip_file) + robot_ips = robot_json.get("ip_address_list") + ip_file.close() + else: + robot_ips = [robot_to_push] + + # Push folder to robots + for robot in robot_ips: + print_proc = multiprocessing.Process( + target=print, args=(f"Pushing {folder_name} to {robot}!\n\n",) + ) + print_proc.start() + print_proc.join() + push_cmd = cmd.format(folder=folder_name, ip=robot) + process = multiprocessing.Process(target=push_subroutine, args=(push_cmd,)) + process.start() + process.join() + print_proc = multiprocessing.Process(target=print, args=("Done!\n\n",)) + print_proc.start() + print_proc.join() + return 0 + + +if __name__ == "__main__": + for i, folder in enumerate(folders): + print(f"{i}) {folder}") + folder_to_push = input("Please Select a Folder to Push: ") + robot_to_push = input("Type in robots ip (type all for all): ") + print(main(folder_to_push, robot_to_push)) diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[004ebb2b82][OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[004ebb2b82][OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift].json index 3d8b4b072eb..f59c9684e23 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[004ebb2b82][OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[004ebb2b82][OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift].json @@ -16668,6 +16668,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.11" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[00574c503f][pl_BacteriaInoculation_Flex_6plates].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[00574c503f][pl_BacteriaInoculation_Flex_6plates].json index ddb334a58e0..05fa920a764 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[00574c503f][pl_BacteriaInoculation_Flex_6plates].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[00574c503f][pl_BacteriaInoculation_Flex_6plates].json @@ -41584,6 +41584,7 @@ "location": "offDeck" } ], + "liquidClasses": [], "liquids": [ { "description": "Bacterial culture medium (e.g., LB broth)", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[011481812b][OT2_S_v2_7_P20S_None_Walkthrough].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[011481812b][OT2_S_v2_7_P20S_None_Walkthrough].json index 38872b09ff8..c709366a42a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[011481812b][OT2_S_v2_7_P20S_None_Walkthrough].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[011481812b][OT2_S_v2_7_P20S_None_Walkthrough].json @@ -4919,6 +4919,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.7", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[01255c3f3b][pl_Flex_Protein_Digestion_Protocol].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[01255c3f3b][pl_Flex_Protein_Digestion_Protocol].json index aac975221e8..2da7a9c47bd 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[01255c3f3b][pl_Flex_Protein_Digestion_Protocol].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[01255c3f3b][pl_Flex_Protein_Digestion_Protocol].json @@ -11824,6 +11824,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json index ff626992e43..4f1452dcdfc 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json @@ -11452,6 +11452,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json index 8cd99860d7e..39491fae6aa 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json @@ -2917,6 +2917,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json index c62ceb23edd..a561da0a387 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json @@ -3113,6 +3113,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[057de2035d][OT2_S_v2_19_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[057de2035d][OT2_S_v2_19_P300M_P20S_HS_TC_TM_SmokeTestV3].json index a2aca7e252a..fe3d81be11b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[057de2035d][OT2_S_v2_19_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[057de2035d][OT2_S_v2_19_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -9569,6 +9569,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0903a95825][Flex_S_v2_19_QIASeq_FX_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0903a95825][Flex_S_v2_19_QIASeq_FX_48x].json index bce38cbe476..f85b03c5703 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0903a95825][Flex_S_v2_19_QIASeq_FX_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0903a95825][Flex_S_v2_19_QIASeq_FX_48x].json @@ -66156,6 +66156,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09676b9f7e][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_west].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09676b9f7e][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_west].json index 2ca289680ef..0f7d7d308b5 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09676b9f7e][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_west].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09676b9f7e][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_west].json @@ -1481,6 +1481,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09ba51132a][OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09ba51132a][OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json index 7a7269decb6..d9895fb2c9e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09ba51132a][OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09ba51132a][OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json @@ -154,6 +154,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0a9ef592c8][Flex_S_v2_18_Illumina_DNA_Prep_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0a9ef592c8][Flex_S_v2_18_Illumina_DNA_Prep_48x].json index 4891466d0b7..f892fc456ce 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0a9ef592c8][Flex_S_v2_18_Illumina_DNA_Prep_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0a9ef592c8][Flex_S_v2_18_Illumina_DNA_Prep_48x].json @@ -49707,6 +49707,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "CleanupBead Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0affe60373][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_maximum].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0affe60373][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_maximum].json index 64072eb8834..a877268d0bd 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0affe60373][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_maximum].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0affe60373][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_maximum].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0b42cfc151][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_row].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0b42cfc151][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_row].json index dfef8b35364..3a0f63a8f99 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0b42cfc151][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_row].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0b42cfc151][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_row].json @@ -1481,6 +1481,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json index 0096a483ffe..957e685c737 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -17072,6 +17072,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0cbde10c37][OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0cbde10c37][OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3].json index e4924262e1a..35dc7ecc804 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0cbde10c37][OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0cbde10c37][OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -9569,6 +9569,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0dd21c0ee5][pl_EM_seq_48Samples_AllSteps_Edits_150].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0dd21c0ee5][pl_EM_seq_48Samples_AllSteps_Edits_150].json index 7bff37154bf..db42ce35fdc 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0dd21c0ee5][pl_EM_seq_48Samples_AllSteps_Edits_150].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0dd21c0ee5][pl_EM_seq_48Samples_AllSteps_Edits_150].json @@ -49392,6 +49392,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[109b7ad1f0][Flex_S_v2_20_96_None_ROW_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[109b7ad1f0][Flex_S_v2_20_96_None_ROW_HappyPath].json index d0b11f42740..cf56c96470e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[109b7ad1f0][Flex_S_v2_20_96_None_ROW_HappyPath].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[109b7ad1f0][Flex_S_v2_20_96_None_ROW_HappyPath].json @@ -6263,6 +6263,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "96 channel pipette and a ROW partial tip configuration.", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10c2386b92][Flex_S_v2_20_96_AllCorners].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10c2386b92][Flex_S_v2_20_96_AllCorners].json index f7457a3c48d..e4de2f89a14 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10c2386b92][Flex_S_v2_20_96_AllCorners].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10c2386b92][Flex_S_v2_20_96_AllCorners].json @@ -33697,6 +33697,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[11020a4e17][pl_Bradford_proteinassay].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[11020a4e17][pl_Bradford_proteinassay].json index 6fb9e302070..7ce2978d56a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[11020a4e17][pl_Bradford_proteinassay].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[11020a4e17][pl_Bradford_proteinassay].json @@ -19352,6 +19352,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Coomassie Brilliant Blue G-250 solution ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[134037b2aa][OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[134037b2aa][OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods].json index f2c63721b33..d33b6cf51cb 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[134037b2aa][OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[134037b2aa][OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods].json @@ -7997,6 +7997,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13e5a9c68b][Flex_S_v2_20_P8X1000_P50_LLD].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13e5a9c68b][Flex_S_v2_20_P8X1000_P50_LLD].json index c463feb0552..8fece97c06c 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13e5a9c68b][Flex_S_v2_20_P8X1000_P50_LLD].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13e5a9c68b][Flex_S_v2_20_P8X1000_P50_LLD].json @@ -6203,6 +6203,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "water for ER testing", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13ec753045][Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13ec753045][Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero].json index 0b2e524dee6..c30b18aa93e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13ec753045][Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13ec753045][Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero].json @@ -4492,6 +4492,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Dandra Howell ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[15a60fccf4][pl_microBioID_beads_touchtip].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[15a60fccf4][pl_microBioID_beads_touchtip].json index 6053323ac4b..3f500210e5a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[15a60fccf4][pl_microBioID_beads_touchtip].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[15a60fccf4][pl_microBioID_beads_touchtip].json @@ -34590,6 +34590,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[194e3c49bb][pl_Normalization_with_PCR].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[194e3c49bb][pl_Normalization_with_PCR].json index ababd25acfa..059f375baec 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[194e3c49bb][pl_Normalization_with_PCR].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[194e3c49bb][pl_Normalization_with_PCR].json @@ -9297,6 +9297,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Rami Farawi ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b9e0f93d9][OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b9e0f93d9][OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks].json index 858286887b6..fd7b30ca845 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b9e0f93d9][OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b9e0f93d9][OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks].json @@ -19714,6 +19714,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "OT-2 protocol with 1ch and 8ch pipette partial/single tip configurations. Mixing tipracks and using separate tipracks. ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4cb705bdbf][Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4cb705bdbf][Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol].json index d810bd75c88..b63443781ac 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4cb705bdbf][Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4cb705bdbf][Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol].json @@ -103,6 +103,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4ea9d66206][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4ea9d66206][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json index 90bfa119fb7..a126374479b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4ea9d66206][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4ea9d66206][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json @@ -1241,6 +1241,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4f50c02c81][Flex_S_v2_19_AMPure_XP_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4f50c02c81][Flex_S_v2_19_AMPure_XP_48x].json index 3af042768f6..f102cab8bc5 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4f50c02c81][Flex_S_v2_19_AMPure_XP_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4f50c02c81][Flex_S_v2_19_AMPure_XP_48x].json @@ -28213,6 +28213,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4fadc166c0][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_variable_name].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4fadc166c0][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_variable_name].json index 843078fa552..484c6600849 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4fadc166c0][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_variable_name].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4fadc166c0][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_variable_name].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[50d7be4518][pl_Zymo_Quick_RNA_Cells_Flex_multi].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[50d7be4518][pl_Zymo_Quick_RNA_Cells_Flex_multi].json index dfc888c15b5..3a839b9cdbd 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[50d7be4518][pl_Zymo_Quick_RNA_Cells_Flex_multi].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[50d7be4518][pl_Zymo_Quick_RNA_Cells_Flex_multi].json @@ -23420,6 +23420,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Cells in DNA/ RNA Shield", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51a761307d][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultOutOfRangeRTP_Override_default_greater_than_maximum].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51a761307d][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultOutOfRangeRTP_Override_default_greater_than_maximum].json index d2955132ff2..72f8481bc29 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51a761307d][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultOutOfRangeRTP_Override_default_greater_than_maximum].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51a761307d][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultOutOfRangeRTP_Override_default_greater_than_maximum].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Default not in range" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51fc977577][OT2_S_v6_P300M_P20S_MixTransferManyLiquids].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51fc977577][OT2_S_v6_P300M_P20S_MixTransferManyLiquids].json index 2b447932025..4c45089bc7c 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51fc977577][OT2_S_v6_P300M_P20S_MixTransferManyLiquids].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51fc977577][OT2_S_v6_P300M_P20S_MixTransferManyLiquids].json @@ -6064,6 +6064,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[53db9bf516][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[53db9bf516][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json index 0aaa562c15c..58b6e3ffb42 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[53db9bf516][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[53db9bf516][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json @@ -1284,6 +1284,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[549cc904ac][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_c3_right_edge].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[549cc904ac][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_c3_right_edge].json index 952985449d9..dde453f20ab 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[549cc904ac][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_c3_right_edge].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[549cc904ac][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_c3_right_edge].json @@ -1249,6 +1249,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54b0b509cd][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54b0b509cd][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json index d28023877a0..8cb5125c17c 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54b0b509cd][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54b0b509cd][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json @@ -2395,6 +2395,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json index 9cad51f6d80..27656b80cca 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json @@ -1894,6 +1894,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[555b2fff00][Flex_S_v2_19_Illumina_DNA_Prep_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[555b2fff00][Flex_S_v2_19_Illumina_DNA_Prep_48x].json index c5c5f1a2e67..84bff8651d2 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[555b2fff00][Flex_S_v2_19_Illumina_DNA_Prep_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[555b2fff00][Flex_S_v2_19_Illumina_DNA_Prep_48x].json @@ -49707,6 +49707,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "CleanupBead Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json index 7c04e4274de..63ed50d9c04 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json @@ -55,6 +55,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[59b00713a7][Flex_S_v2_18_ligseq].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[59b00713a7][Flex_S_v2_18_ligseq].json index 3646ae2d522..4744b1f1992 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[59b00713a7][Flex_S_v2_18_ligseq].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[59b00713a7][Flex_S_v2_18_ligseq].json @@ -22844,6 +22844,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5c57add326][pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5c57add326][pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel].json index e608af8c173..353a1b46f45 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5c57add326][pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5c57add326][pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel].json @@ -46193,6 +46193,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Sample Resuspended in PBS", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json index c76b2aca7f9..059e7fc2b84 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json @@ -1258,6 +1258,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5edb9b4de3][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_2].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5edb9b4de3][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_2].json index a107fa87e60..47c65a0dfc5 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5edb9b4de3][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_2].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5edb9b4de3][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_2].json @@ -1241,6 +1241,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60015c6e65][OT2_X_v2_18_None_None_duplicateRTPVariableName].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60015c6e65][OT2_X_v2_18_None_None_duplicateRTPVariableName].json index 86d3274f412..3c69dda38e7 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60015c6e65][OT2_X_v2_18_None_None_duplicateRTPVariableName].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60015c6e65][OT2_X_v2_18_None_None_duplicateRTPVariableName].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Multiple RTP Variables with Same Name" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[604023f7f1][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[604023f7f1][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3].json index 0de0eff0022..fde783d94b8 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[604023f7f1][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[604023f7f1][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3].json @@ -197,6 +197,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60c1d39463][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_int_default_no_matching_choices].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60c1d39463][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_int_default_no_matching_choices].json index 726906c04d4..b8ef1cbc5f2 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60c1d39463][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_int_default_no_matching_choices].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60c1d39463][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_int_default_no_matching_choices].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "default choice does not match a choice" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6122c72996][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_1].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6122c72996][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_1].json index 1dcac6e453a..180178d1d44 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6122c72996][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_1].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6122c72996][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_1].json @@ -1241,6 +1241,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json index d8409d8db46..8623a021746 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json @@ -55,6 +55,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[61619d5498][Flex_S_v2_18_NO_PIPETTES_GoldenRTP].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[61619d5498][Flex_S_v2_18_NO_PIPETTES_GoldenRTP].json index afa5bb0b4d2..8b06eca9390 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[61619d5498][Flex_S_v2_18_NO_PIPETTES_GoldenRTP].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[61619d5498][Flex_S_v2_18_NO_PIPETTES_GoldenRTP].json @@ -295,6 +295,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Golden RTP Examples" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[618f29898f][pl_Flex_customizable_serial_dilution_upload].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[618f29898f][pl_Flex_customizable_serial_dilution_upload].json index 385da3c78a4..b1528f23cbf 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[618f29898f][pl_Flex_customizable_serial_dilution_upload].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[618f29898f][pl_Flex_customizable_serial_dilution_upload].json @@ -10386,6 +10386,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Diluent liquid is filled in the reservoir", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[63ea171b47][pl_M_N_Nucleomag_DNA_Flex_multi].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[63ea171b47][pl_M_N_Nucleomag_DNA_Flex_multi].json index 5681dc28194..1441d3d1cac 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[63ea171b47][pl_M_N_Nucleomag_DNA_Flex_multi].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[63ea171b47][pl_M_N_Nucleomag_DNA_Flex_multi].json @@ -16386,6 +16386,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Cell Pellet", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[675d2c2562][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_east].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[675d2c2562][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_east].json index 1bf35620512..d27c90a866c 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[675d2c2562][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_east].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[675d2c2562][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_east].json @@ -1481,6 +1481,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[68614da0b3][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_east].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[68614da0b3][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_east].json index b2ec113fe4e..7209e028a2b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[68614da0b3][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_east].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[68614da0b3][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_east].json @@ -1481,6 +1481,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6ad5590adf][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_unit].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6ad5590adf][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_unit].json index e545da56bd4..e30b5bee0d8 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6ad5590adf][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_unit].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6ad5590adf][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_unit].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6c20d6c570][Flex_S_v2_20_96_None_COLUMN_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6c20d6c570][Flex_S_v2_20_96_None_COLUMN_HappyPath].json index 19ac0d4e0f7..3ac36a59ee5 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6c20d6c570][Flex_S_v2_20_96_None_COLUMN_HappyPath].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6c20d6c570][Flex_S_v2_20_96_None_COLUMN_HappyPath].json @@ -6263,6 +6263,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "96 channel pipette and a COLUMN partial tip configuration.", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6cee20a957][OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6cee20a957][OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError].json index 8f88134625a..da1993d6e56 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6cee20a957][OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6cee20a957][OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError].json @@ -94,6 +94,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.12", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e2f6f10c5][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_destination_collision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e2f6f10c5][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_destination_collision].json index cf3e8bf4aa3..0e079e7daa2 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e2f6f10c5][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_destination_collision].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e2f6f10c5][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_destination_collision].json @@ -3946,6 +3946,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json index 66877246558..eba57a84196 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json @@ -56935,6 +56935,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json index 63567ca7c96..f052823d867 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json @@ -512,6 +512,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Heater-shaker conflict OT-2" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e744cbb48][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_str_default_no_matching_choices].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e744cbb48][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_str_default_no_matching_choices].json index cae3345ff13..2b5614762ba 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e744cbb48][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_str_default_no_matching_choices].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e744cbb48][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_str_default_no_matching_choices].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "default choice does not match a choice" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f246e1cd8][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f246e1cd8][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4].json index d67ff04865b..fd1c3550795 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f246e1cd8][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f246e1cd8][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4].json @@ -45393,6 +45393,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f3e297a11][OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f3e297a11][OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix].json index 80a9f7d117a..0028c36df1b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f3e297a11][OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f3e297a11][OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix].json @@ -3435,6 +3435,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.3" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json index ad8638a9e6d..86023eb8c12 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json @@ -2849,6 +2849,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[70b873c24b][pl_SamplePrep_MS_Digest_Flex_upto96].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[70b873c24b][pl_SamplePrep_MS_Digest_Flex_upto96].json index cbd7839e9ad..b79aec33a1b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[70b873c24b][pl_SamplePrep_MS_Digest_Flex_upto96].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[70b873c24b][pl_SamplePrep_MS_Digest_Flex_upto96].json @@ -65064,6 +65064,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "100 mM ABC in MS grade water, volume per well", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[733c9cdf62][Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[733c9cdf62][Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath].json index 1b70c59e4b6..a79c72e6781 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[733c9cdf62][Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[733c9cdf62][Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath].json @@ -5042,6 +5042,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "Tip Rack South Clearance for the 8 Channel pipette and a SINGLE partial tip configuration.", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7583faec7c][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_return_tip_error].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7583faec7c][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_return_tip_error].json index 30ddffb8e03..3a2911f043d 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7583faec7c][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_return_tip_error].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7583faec7c][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_return_tip_error].json @@ -4920,6 +4920,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[770ebdcd29][pl_KAPA_Library_Quant_48_v8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[770ebdcd29][pl_KAPA_Library_Quant_48_v8].json index bc24730fad8..c577f539508 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[770ebdcd29][pl_KAPA_Library_Quant_48_v8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[770ebdcd29][pl_KAPA_Library_Quant_48_v8].json @@ -31929,6 +31929,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Dilution Buffer", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[79e61426a2][Flex_S_v2_18_AMPure_XP_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[79e61426a2][Flex_S_v2_18_AMPure_XP_48x].json index 19cf70d2edb..00efc4b9178 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[79e61426a2][Flex_S_v2_18_AMPure_XP_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[79e61426a2][Flex_S_v2_18_AMPure_XP_48x].json @@ -28213,6 +28213,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d06568bfe][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_display_name].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d06568bfe][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_display_name].json index bd4f009a701..ae9e8d99862 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d06568bfe][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_display_name].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d06568bfe][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_display_name].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7e7a90041b][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_west_column].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7e7a90041b][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_west_column].json index df37cc2db4b..48077d59118 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7e7a90041b][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_west_column].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7e7a90041b][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_west_column].json @@ -1481,6 +1481,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7ebefe4580][pl_QIAseq_FX_24x_Normalizer_Workflow_B].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7ebefe4580][pl_QIAseq_FX_24x_Normalizer_Workflow_B].json index 47ce454e920..ac4311409dc 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7ebefe4580][pl_QIAseq_FX_24x_Normalizer_Workflow_B].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7ebefe4580][pl_QIAseq_FX_24x_Normalizer_Workflow_B].json @@ -75905,6 +75905,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7f2ef0eaff][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_float_default_no_matching_choices].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7f2ef0eaff][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_float_default_no_matching_choices].json index 0c559ae74b3..235d5eb9fe3 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7f2ef0eaff][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_float_default_no_matching_choices].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7f2ef0eaff][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_float_default_no_matching_choices].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "default choice does not match a choice" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json index 44584111a12..e0fd663c213 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json @@ -55,6 +55,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8455adcea9][OT2_S_v2_12_P300M_P20S_FailOnRun].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8455adcea9][OT2_S_v2_12_P300M_P20S_FailOnRun].json index 63aed19f5f3..d4cf07c0f99 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8455adcea9][OT2_S_v2_12_P300M_P20S_FailOnRun].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8455adcea9][OT2_S_v2_12_P300M_P20S_FailOnRun].json @@ -2666,6 +2666,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.12", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[84f684cbc4][Flex_S_v2_18_IDT_xGen_EZ_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[84f684cbc4][Flex_S_v2_18_IDT_xGen_EZ_48x].json index 80ce54abbcb..2312c3a011e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[84f684cbc4][Flex_S_v2_18_IDT_xGen_EZ_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[84f684cbc4][Flex_S_v2_18_IDT_xGen_EZ_48x].json @@ -59919,6 +59919,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json index 9f3a0d8a1fb..ac524674f7e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -14372,6 +14372,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88abcfdbca][pl_Zymo_Quick_RNA_Cells_Flex_96_Channel].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88abcfdbca][pl_Zymo_Quick_RNA_Cells_Flex_96_Channel].json index 3cc6db1a5cd..7fb0dceab92 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88abcfdbca][pl_Zymo_Quick_RNA_Cells_Flex_96_Channel].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88abcfdbca][pl_Zymo_Quick_RNA_Cells_Flex_96_Channel].json @@ -49169,6 +49169,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Sample Volume in Shield", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88de2fb78f][OT2_X_v6_P20S_P300M_HS_HSCollision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88de2fb78f][OT2_X_v6_P20S_P300M_HS_HSCollision].json index 5d219d91f72..7eedccb2cf8 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88de2fb78f][OT2_X_v6_P20S_P300M_HS_HSCollision].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88de2fb78f][OT2_X_v6_P20S_P300M_HS_HSCollision].json @@ -5373,6 +5373,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a255db0b][OT2_X_v2_18_None_None_StrRTPwith_unit].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a255db0b][OT2_X_v2_18_None_None_StrRTPwith_unit].json index 2b9cd2584d3..70bb212b45b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a255db0b][OT2_X_v2_18_None_None_StrRTPwith_unit].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a255db0b][OT2_X_v2_18_None_None_StrRTPwith_unit].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Str RTP with unit" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json index 47511dff64f..ea3c1cc76b0 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json @@ -4997,6 +4997,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8a663305c4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8a663305c4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south].json index e2fadc01642..cadf197c142 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8a663305c4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8a663305c4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south].json @@ -1481,6 +1481,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8b07e799f6][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8b07e799f6][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json index 919f1980537..e433acf53ff 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8b07e799f6][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8b07e799f6][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json @@ -3766,6 +3766,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8e1f35ed6c][pl_NiNTA_Flex_96well_PlatePrep_final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8e1f35ed6c][pl_NiNTA_Flex_96well_PlatePrep_final].json index b8dd13f5f42..c21c19205cf 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8e1f35ed6c][pl_NiNTA_Flex_96well_PlatePrep_final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8e1f35ed6c][pl_NiNTA_Flex_96well_PlatePrep_final].json @@ -18477,6 +18477,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Equilibration Buffer", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json index 1d83bf0706f..933aa66cf7d 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json @@ -3727,6 +3727,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[918747b2f9][pl_Illumina_DNA_Prep_48x_v8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[918747b2f9][pl_Illumina_DNA_Prep_48x_v8].json index 349fbd62034..794499f75ce 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[918747b2f9][pl_Illumina_DNA_Prep_48x_v8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[918747b2f9][pl_Illumina_DNA_Prep_48x_v8].json @@ -52167,6 +52167,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "CleanupBead Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[93b724671e][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[93b724671e][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json index 2c3d142321b..d9f59af3587 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[93b724671e][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[93b724671e][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json @@ -2468,6 +2468,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[94913d2988][OT2_S_v3_P300SGen1_None_Gen1PipetteSimple].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[94913d2988][OT2_S_v3_P300SGen1_None_Gen1PipetteSimple].json index 8c086d8fdff..405df785df9 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[94913d2988][OT2_S_v3_P300SGen1_None_Gen1PipetteSimple].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[94913d2988][OT2_S_v3_P300SGen1_None_Gen1PipetteSimple].json @@ -6474,6 +6474,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[95da6fbef2][Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[95da6fbef2][Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria].json index 85ee931590d..004f5251126 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[95da6fbef2][Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[95da6fbef2][Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria].json @@ -32273,6 +32273,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Zach Galluzzo ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json index cbad73a3a2d..8a871949e46 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json @@ -2775,6 +2775,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.11" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[973fa979e6][Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[973fa979e6][Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json index cbf301b89e7..5538166da59 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[973fa979e6][Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[973fa979e6][Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json @@ -171,6 +171,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9bcb0a3f13][pl_normalization_with_csv].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9bcb0a3f13][pl_normalization_with_csv].json index b0f0b8ac0bd..23fd7f389a0 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9bcb0a3f13][pl_normalization_with_csv].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9bcb0a3f13][pl_normalization_with_csv].json @@ -6024,6 +6024,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Krishna Soma ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json index 8d4e3a960dd..aba00388845 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json @@ -1435,6 +1435,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a01a35c14a][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a01a35c14a][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3].json index 5a508d84d58..7cb88cd0308 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a01a35c14a][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a01a35c14a][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3].json @@ -153,6 +153,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a06502b2dc][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_description].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a06502b2dc][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_description].json index 7808bbc2d03..e2a5dced311 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a06502b2dc][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_description].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a06502b2dc][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_description].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json index f951219fdff..5bc309d3cac 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json @@ -9575,6 +9575,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0b755a1a1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_west].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0b755a1a1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_west].json index eca34fc28c3..68185db5dbd 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0b755a1a1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_west].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0b755a1a1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_west].json @@ -1481,6 +1481,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0dad2eb8e][pl_SamplePrep_MS_Cleanup_Flex_upto96].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0dad2eb8e][pl_SamplePrep_MS_Cleanup_Flex_upto96].json index 965ca7d3ead..7aecea25f6a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0dad2eb8e][pl_SamplePrep_MS_Cleanup_Flex_upto96].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0dad2eb8e][pl_SamplePrep_MS_Cleanup_Flex_upto96].json @@ -50265,6 +50265,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Digested Protein samples, volume per well", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a3dfca7f0c][Flex_S_v2_19_Illumina_DNA_PCR_Free].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a3dfca7f0c][Flex_S_v2_19_Illumina_DNA_PCR_Free].json index 1c3e57b481a..36400ae7de7 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a3dfca7f0c][Flex_S_v2_19_Illumina_DNA_PCR_Free].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a3dfca7f0c][Flex_S_v2_19_Illumina_DNA_PCR_Free].json @@ -27030,6 +27030,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a437534569][Flex_S_v2_19_kapahyperplus].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a437534569][Flex_S_v2_19_kapahyperplus].json index 42781ff6ea1..fe8184c0608 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a437534569][Flex_S_v2_19_kapahyperplus].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a437534569][Flex_S_v2_19_kapahyperplus].json @@ -29144,6 +29144,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a46928c103][pl_Nanopore_Genomic_Ligation_v5_Final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a46928c103][pl_Nanopore_Genomic_Ligation_v5_Final].json index be8c1a00d13..e623aec42f7 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a46928c103][pl_Nanopore_Genomic_Ligation_v5_Final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a46928c103][pl_Nanopore_Genomic_Ligation_v5_Final].json @@ -22844,6 +22844,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a4d3b3a2d3][pl_96_ch_demo_rtp_with_hs].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a4d3b3a2d3][pl_96_ch_demo_rtp_with_hs].json index 8a0a8a6a2ee..0aacb0b3e73 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a4d3b3a2d3][pl_96_ch_demo_rtp_with_hs].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a4d3b3a2d3][pl_96_ch_demo_rtp_with_hs].json @@ -16593,6 +16593,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "generic", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a66d700ed6][OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a66d700ed6][OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3].json index 321a04e20ac..e6cb5eace9a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a66d700ed6][OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a66d700ed6][OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -14092,6 +14092,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8528e52b4][Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8528e52b4][Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide].json index f4e89bf46a3..b0eb2e93f00 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8528e52b4][Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8528e52b4][Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide].json @@ -9431,6 +9431,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "Unsafe protocol ❗❗❗❗❗❗❗❗❗❗❗ will collide with tube.", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8a5ad823d][pl_cherrypicking_flex_v3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8a5ad823d][pl_cherrypicking_flex_v3].json index 6afef67d006..86a33113a16 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8a5ad823d][pl_cherrypicking_flex_v3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8a5ad823d][pl_cherrypicking_flex_v3].json @@ -10373,6 +10373,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Krishna Soma ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a94f22054f][Flex_S_v2_18_kapahyperplus].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a94f22054f][Flex_S_v2_18_kapahyperplus].json index 01ce458ff53..52120bd6dc3 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a94f22054f][Flex_S_v2_18_kapahyperplus].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a94f22054f][Flex_S_v2_18_kapahyperplus].json @@ -29144,6 +29144,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json index 60a0f1c77a3..1d043a44952 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json @@ -55,6 +55,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[aa61eee0bf][pl_sigdx_part2].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[aa61eee0bf][pl_sigdx_part2].json index 8e14d013357..93eff2447db 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[aa61eee0bf][pl_sigdx_part2].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[aa61eee0bf][pl_sigdx_part2].json @@ -46385,6 +46385,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac5a46e74b][pl_langone_pt2_ribo].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac5a46e74b][pl_langone_pt2_ribo].json index b4324589435..ec2e77260d2 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac5a46e74b][pl_langone_pt2_ribo].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac5a46e74b][pl_langone_pt2_ribo].json @@ -30083,6 +30083,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "ATL4", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json index ef9acd1b1a3..30f2c70b0ea 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json @@ -15276,6 +15276,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[acefe91275][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[acefe91275][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json index c8389b97d75..1f453e29cf8 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[acefe91275][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[acefe91275][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json @@ -2438,6 +2438,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad627dcedf][OT2_S_v6_P300M_P20S_HS_Smoke620release].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad627dcedf][OT2_S_v6_P300M_P20S_HS_Smoke620release].json index 3a44acf987c..4af69fce36b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad627dcedf][OT2_S_v6_P300M_P20S_HS_Smoke620release].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad627dcedf][OT2_S_v6_P300M_P20S_HS_Smoke620release].json @@ -9492,6 +9492,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad9184067d][Flex_X_v2_18_NO_PIPETTES_ReservedWord].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad9184067d][Flex_X_v2_18_NO_PIPETTES_ReservedWord].json index d3338855040..ed08b660a33 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad9184067d][Flex_X_v2_18_NO_PIPETTES_ReservedWord].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad9184067d][Flex_X_v2_18_NO_PIPETTES_ReservedWord].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Default not in range" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json index f86080f047c..a18485392e9 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json @@ -6240,6 +6240,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[afd5d372a9][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_return_tip_error].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[afd5d372a9][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_return_tip_error].json index a5b5bdb65cc..9f12179d1e2 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[afd5d372a9][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_return_tip_error].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[afd5d372a9][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_return_tip_error].json @@ -3766,6 +3766,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json index 5b0df3b070c..b41b7117e24 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json @@ -3706,6 +3706,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b48407ff98][pl_cherrypicking_csv_airgap].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b48407ff98][pl_cherrypicking_csv_airgap].json index 4433e026fd1..6dffb02e16c 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b48407ff98][pl_cherrypicking_csv_airgap].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b48407ff98][pl_cherrypicking_csv_airgap].json @@ -28815,6 +28815,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Samples", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b777168ac1][Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b777168ac1][Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero].json index 7005e6011ab..43f62a32282 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b777168ac1][Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b777168ac1][Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero].json @@ -4492,6 +4492,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Dandra Howell ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b79134ab8a][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b79134ab8a][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json index b3637624ed4..7956e369c52 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b79134ab8a][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b79134ab8a][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json @@ -4920,6 +4920,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b806f07be9][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_value].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b806f07be9][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_value].json index 6b2391f6118..cf8ec946db5 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b806f07be9][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_value].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b806f07be9][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_value].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b91d31eaa2][pl_MagMax_RNA_Cells_Flex_96_Channel].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b91d31eaa2][pl_MagMax_RNA_Cells_Flex_96_Channel].json index 4bcec7cf7de..db24530e196 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b91d31eaa2][pl_MagMax_RNA_Cells_Flex_96_Channel].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b91d31eaa2][pl_MagMax_RNA_Cells_Flex_96_Channel].json @@ -41229,6 +41229,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Magnetic Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json index 5460d2d1fd7..851fd7e1fbb 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json @@ -125769,6 +125769,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[bc049301c1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_transfer_destination_collision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[bc049301c1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_transfer_destination_collision].json index 290674f3bd6..8c741ed84ba 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[bc049301c1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_transfer_destination_collision].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[bc049301c1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_transfer_destination_collision].json @@ -3912,6 +3912,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c0435f08da][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c0435f08da][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north].json index 67a07aa1297..8c38d6c5f57 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c0435f08da][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c0435f08da][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north].json @@ -1481,6 +1481,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c064d0de2c][OT2_S_v6_P1000S_None_SimpleTransfer].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c064d0de2c][OT2_S_v6_P1000S_None_SimpleTransfer].json index 14cc53aba17..4d2cd13d215 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c064d0de2c][OT2_S_v6_P1000S_None_SimpleTransfer].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c064d0de2c][OT2_S_v6_P1000S_None_SimpleTransfer].json @@ -1977,6 +1977,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c195291f84][OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c195291f84][OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json index d6eb8a28124..c2e5c309dcf 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c195291f84][OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c195291f84][OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json @@ -145,6 +145,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c1c04baffd][Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c1c04baffd][Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json index f59969368ab..7c817b2b869 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c1c04baffd][Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c1c04baffd][Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json @@ -171,6 +171,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c3098303ad][OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c3098303ad][OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json index 4c6c38162b3..f69446ee9cb 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c3098303ad][OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c3098303ad][OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json @@ -154,6 +154,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c343dfb5a0][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_bottom_right_edge].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c343dfb5a0][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_bottom_right_edge].json index 4665f21b62e..fe3f96abc67 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c343dfb5a0][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_bottom_right_edge].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c343dfb5a0][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_bottom_right_edge].json @@ -1249,6 +1249,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json index aadd38b4eaa..1a48b84ae0a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json @@ -12315,6 +12315,7 @@ "location": "offDeck" } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c821e64fad][OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c821e64fad][OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release].json index 27e9d4f2c51..253a2bcff9d 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c821e64fad][OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c821e64fad][OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release].json @@ -10777,6 +10777,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.13", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c8d2ca0089][Flex_S_v2_18_QIASeq_FX_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c8d2ca0089][Flex_S_v2_18_QIASeq_FX_48x].json index 131c7514649..06153dd11b4 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c8d2ca0089][Flex_S_v2_18_QIASeq_FX_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c8d2ca0089][Flex_S_v2_18_QIASeq_FX_48x].json @@ -66156,6 +66156,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json index 5efbff81ebc..cf748ae6fa0 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json @@ -7184,6 +7184,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "NN MM", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cb5adc3d23][OT2_S_v6_P20S_P300M_TransferReTransferLiquid].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cb5adc3d23][OT2_S_v6_P20S_P300M_TransferReTransferLiquid].json index 1759b7b244f..ac74dbfc2a4 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cb5adc3d23][OT2_S_v6_P20S_P300M_TransferReTransferLiquid].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cb5adc3d23][OT2_S_v6_P20S_P300M_TransferReTransferLiquid].json @@ -12796,6 +12796,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cc26c104b4][Flex_S_v2_20_8_None_SINGLE_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cc26c104b4][Flex_S_v2_20_8_None_SINGLE_HappyPath].json index 4ad4434ab42..c9afc886f56 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cc26c104b4][Flex_S_v2_20_8_None_SINGLE_HappyPath].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cc26c104b4][Flex_S_v2_20_8_None_SINGLE_HappyPath].json @@ -7179,6 +7179,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "Tip Rack South Clearance for the 8 Channel pipette and a SINGLE partial tip configuration.", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cecd51c8ee][pl_ExpressPlex_96_final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cecd51c8ee][pl_ExpressPlex_96_final].json index 4e8f71a17c1..b591039cbbb 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cecd51c8ee][pl_ExpressPlex_96_final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cecd51c8ee][pl_ExpressPlex_96_final].json @@ -13346,6 +13346,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Index Plate color", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d0154b1493][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d0154b1493][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json index 1149640d8b1..f449eff0d94 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d0154b1493][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d0154b1493][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json @@ -1314,6 +1314,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d14738bdfe][pl_Zymo_Magbead_DNA_Cells_Flex_multi].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d14738bdfe][pl_Zymo_Magbead_DNA_Cells_Flex_multi].json index 6e02fa8a3f3..a4d46be3d94 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d14738bdfe][pl_Zymo_Magbead_DNA_Cells_Flex_multi].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d14738bdfe][pl_Zymo_Magbead_DNA_Cells_Flex_multi].json @@ -18032,6 +18032,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Samples", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d29d74d7fb][pl_QIASeq_FX_48x_v8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d29d74d7fb][pl_QIASeq_FX_48x_v8].json index 9d35aba10fc..2f0c52a853f 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d29d74d7fb][pl_QIASeq_FX_48x_v8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d29d74d7fb][pl_QIASeq_FX_48x_v8].json @@ -70949,6 +70949,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "CleanupBead Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d2c818bf00][Flex_S_v2_20_P50_LPD].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d2c818bf00][Flex_S_v2_20_P50_LPD].json index 52e87c76f46..1b664b4e963 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d2c818bf00][Flex_S_v2_20_P50_LPD].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d2c818bf00][Flex_S_v2_20_P50_LPD].json @@ -5113,6 +5113,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Test this wet!!!", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d391213095][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d391213095][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria].json index 6f5f1f09b83..7fd14d2f851 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d391213095][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d391213095][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria].json @@ -25208,6 +25208,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Zach Galluzzo ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d3b28ea1d7][pl_Zymo_Magbead_DNA_Cells_Flex_96_channel].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d3b28ea1d7][pl_Zymo_Magbead_DNA_Cells_Flex_96_channel].json index f0d2d744031..7916f424286 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d3b28ea1d7][pl_Zymo_Magbead_DNA_Cells_Flex_96_channel].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d3b28ea1d7][pl_Zymo_Magbead_DNA_Cells_Flex_96_channel].json @@ -40886,6 +40886,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Lysis Buffer", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json index 3056b873a74..718e0a0df13 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -17430,6 +17430,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6026e11c5][OT2_X_v2_7_P300S_TwinningError].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6026e11c5][OT2_X_v2_7_P300S_TwinningError].json index 026977dbcc6..5b6c3c3c690 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6026e11c5][OT2_X_v2_7_P300S_TwinningError].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6026e11c5][OT2_X_v2_7_P300S_TwinningError].json @@ -2836,6 +2836,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.7", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d61739e6a3][Flex_S_v2_19_IDT_xGen_EZ_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d61739e6a3][Flex_S_v2_19_IDT_xGen_EZ_48x].json index 99ccd21cc19..170de395195 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d61739e6a3][Flex_S_v2_19_IDT_xGen_EZ_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d61739e6a3][Flex_S_v2_19_IDT_xGen_EZ_48x].json @@ -59919,6 +59919,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6389183c0][pl_AMPure_XP_48x_v8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6389183c0][pl_AMPure_XP_48x_v8].json index 6b342319f31..1ad848a9ef8 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6389183c0][pl_AMPure_XP_48x_v8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6389183c0][pl_AMPure_XP_48x_v8].json @@ -28213,6 +28213,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d7e862d601][OT2_S_v2_18_None_None_duplicateChoiceValue].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d7e862d601][OT2_S_v2_18_None_None_duplicateChoiceValue].json index 7ea850030fd..87b61a0454d 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d7e862d601][OT2_S_v2_18_None_None_duplicateChoiceValue].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d7e862d601][OT2_S_v2_18_None_None_duplicateChoiceValue].json @@ -43,6 +43,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Duplicate choice value" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json index 1e9b318abf5..aba7dd56957 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json @@ -3591,6 +3591,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d979799443][OT2_S_v2_20_8_None_SINGLE_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d979799443][OT2_S_v2_20_8_None_SINGLE_HappyPath].json index 65c2da26059..bd408636813 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d979799443][OT2_S_v2_20_8_None_SINGLE_HappyPath].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d979799443][OT2_S_v2_20_8_None_SINGLE_HappyPath].json @@ -8241,6 +8241,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "OT2 8 Channel pipette and a SINGLE partial tip configuration.", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[da326082e1][pl_Hyperplus_tiptracking_V4_final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[da326082e1][pl_Hyperplus_tiptracking_V4_final].json index 8b7cf7214ac..2651a003e75 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[da326082e1][pl_Hyperplus_tiptracking_V4_final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[da326082e1][pl_Hyperplus_tiptracking_V4_final].json @@ -29144,6 +29144,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dabb7872d8][Flex_S_v2_19_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dabb7872d8][Flex_S_v2_19_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json index b262ea72c0f..c52ed516ba1 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dabb7872d8][Flex_S_v2_19_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dabb7872d8][Flex_S_v2_19_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json @@ -13164,6 +13164,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[db1fae41ec][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[db1fae41ec][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_3].json index beb0aa09c29..62ea1e316b2 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[db1fae41ec][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[db1fae41ec][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_3].json @@ -1241,6 +1241,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dbba7a71a8][OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dbba7a71a8][OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json index 2288dccf926..9e3cf07280a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dbba7a71a8][OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dbba7a71a8][OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json @@ -145,6 +145,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json index 0353b26aed1..2eb5308529f 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json @@ -171,6 +171,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Thermocycler conflict 1" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e0b0133ffb][pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e0b0133ffb][pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol].json index b22e56cb8ed..1bb680c2c4f 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e0b0133ffb][pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e0b0133ffb][pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol].json @@ -39234,6 +39234,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Dandra Howell ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e18bdd6f5d][Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e18bdd6f5d][Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8].json index 6a1c9e67b51..6c6c30ace61 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e18bdd6f5d][Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e18bdd6f5d][Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8].json @@ -34667,6 +34667,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e42e36e3ca][OT2_X_v2_13_None_None_PythonSyntaxError].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e42e36e3ca][OT2_X_v2_13_None_None_PythonSyntaxError].json index bd05f58334f..0dd0410636f 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e42e36e3ca][OT2_X_v2_13_None_None_PythonSyntaxError].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e42e36e3ca][OT2_X_v2_13_None_None_PythonSyntaxError].json @@ -45,6 +45,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.13", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4660ca6df][OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4660ca6df][OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps].json index 44fbc26f5b6..1e4573b1d8e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4660ca6df][OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4660ca6df][OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps].json @@ -2580,6 +2580,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "AA BB", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e496fec176][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_default].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e496fec176][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_default].json index 013da0c0d7d..f6c1ad84067 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e496fec176][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_default].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e496fec176][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_default].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4b5b30b2e][Flex_S_v2_18_Illumina_DNA_PCR_Free].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4b5b30b2e][Flex_S_v2_18_Illumina_DNA_PCR_Free].json index 7f0ba6fd654..aca7454ff36 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4b5b30b2e][Flex_S_v2_18_Illumina_DNA_PCR_Free].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4b5b30b2e][Flex_S_v2_18_Illumina_DNA_PCR_Free].json @@ -27030,6 +27030,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e71b031f47][pl_Illumina_DNA_PCR_Free].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e71b031f47][pl_Illumina_DNA_PCR_Free].json index a0e23ed018b..803f4133451 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e71b031f47][pl_Illumina_DNA_PCR_Free].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e71b031f47][pl_Illumina_DNA_PCR_Free].json @@ -27030,6 +27030,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e84e23a4ea][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_top_edge].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e84e23a4ea][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_top_edge].json index 3ab5889bbf7..ea9fbf3efb7 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e84e23a4ea][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_top_edge].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e84e23a4ea][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_top_edge].json @@ -1249,6 +1249,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e8f451df45][Flex_S_v2_20_96_None_Column3_SINGLE_].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e8f451df45][Flex_S_v2_20_96_None_Column3_SINGLE_].json index ca6f70d1692..81ebf160345 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e8f451df45][Flex_S_v2_20_96_None_Column3_SINGLE_].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e8f451df45][Flex_S_v2_20_96_None_Column3_SINGLE_].json @@ -26843,6 +26843,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed1e64c539][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed1e64c539][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2].json index 368bbe05d9b..709b448717c 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed1e64c539][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed1e64c539][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2].json @@ -103,6 +103,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed26635ff7][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_source_collision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed26635ff7][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_source_collision].json index 61a7e9595ff..ed5d5a67171 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed26635ff7][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_source_collision].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed26635ff7][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_source_collision].json @@ -3878,6 +3878,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed2f3800b6][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed2f3800b6][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7].json index 00f911388c0..d946aae6d9d 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed2f3800b6][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed2f3800b6][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7].json @@ -40778,6 +40778,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json index 4bcefec1199..d1a7a88d075 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json @@ -1448,6 +1448,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f24bb0b4d9][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f24bb0b4d9][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3].json index d1feceae4d0..d454695d871 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f24bb0b4d9][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f24bb0b4d9][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3].json @@ -18009,6 +18009,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f301704f56][OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f301704f56][OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer].json index 4e89581c149..3d7f6d10b51 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f301704f56][OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f301704f56][OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer].json @@ -7101,6 +7101,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f345e8e33a][OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f345e8e33a][OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40].json index ab9fd95e4c0..d86eae54045 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f345e8e33a][OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f345e8e33a][OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40].json @@ -10615,6 +10615,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "NN MM", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f37bb0ec36][OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f37bb0ec36][OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock].json index b12618b009e..e0c21a82e55 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f37bb0ec36][OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f37bb0ec36][OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock].json @@ -29,6 +29,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json index f8f121ce092..50ab65351e1 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json @@ -15386,6 +15386,7 @@ "location": "offDeck" } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f5f3b9c5bb][Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f5f3b9c5bb][Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict].json index d452cf7ab52..950c5ee4395 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f5f3b9c5bb][Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f5f3b9c5bb][Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict].json @@ -1339,6 +1339,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f639acc89d][Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f639acc89d][Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json index 2c598934321..e4fed39c549 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f639acc89d][Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f639acc89d][Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json @@ -180,6 +180,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f6c1ddbb32][pl_ExpressPlex_Pooling_Final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f6c1ddbb32][pl_ExpressPlex_Pooling_Final].json index 8ca9a88cdbf..920a648041a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f6c1ddbb32][pl_ExpressPlex_Pooling_Final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f6c1ddbb32][pl_ExpressPlex_Pooling_Final].json @@ -36949,6 +36949,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Amplified Libraries_1", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json index 04d54b06b4e..7ad30e9d04e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json @@ -1385,6 +1385,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json index 3152a671909..acf7455e286 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json @@ -14192,6 +14192,7 @@ "location": "offDeck" } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f86713b4d4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_distribute_source_collision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f86713b4d4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_distribute_source_collision].json index 09e15f48097..1fdb58d69ab 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f86713b4d4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_distribute_source_collision].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f86713b4d4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_distribute_source_collision].json @@ -3878,6 +3878,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "description": "oooo", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f88b7d6e30][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_display_name].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f88b7d6e30][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_display_name].json index 1652972327b..fe2e22fae05 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f88b7d6e30][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_display_name].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f88b7d6e30][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_display_name].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json index 13f15c638d0..cd25845d931 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json @@ -3105,6 +3105,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/api-client/src/runs/types.ts b/api-client/src/runs/types.ts index a6279d12145..bf8596b66d7 100644 --- a/api-client/src/runs/types.ts +++ b/api-client/src/runs/types.ts @@ -120,6 +120,7 @@ export interface Runs { export interface RunCurrentStateData { estopEngaged: boolean activeNozzleLayouts: Record // keyed by pipetteId + tipStates: Record // keyed by pipetteId placeLabwareState?: PlaceLabwareState } @@ -218,3 +219,7 @@ export interface PlaceLabwareState { location: OnDeckLabwareLocation shouldPlaceDown: boolean } + +export interface TipStates { + hasTip: boolean +} diff --git a/api/docs/v2/conf.py b/api/docs/v2/conf.py index 5ab0fdaad76..708d800ce3e 100644 --- a/api/docs/v2/conf.py +++ b/api/docs/v2/conf.py @@ -445,7 +445,6 @@ ("py:class", r".*protocol_api\.config.*"), ("py:class", r".*opentrons_shared_data.*"), ("py:class", r".*protocol_api._parameters.Parameters.*"), - ("py:class", r".*AbsorbanceReaderContext"), ("py:class", r".*RobotContext"), # shh it's a secret (for now) ("py:class", r'.*AbstractLabware|APIVersion|LabwareLike|LoadedCoreMap|ModuleTypes|NoneType|OffDeckType|ProtocolCore|WellCore'), # laundry list of not fully qualified things ] diff --git a/api/docs/v2/modules/absorbance_plate_reader.rst b/api/docs/v2/modules/absorbance_plate_reader.rst new file mode 100644 index 00000000000..9f96d5e90d3 --- /dev/null +++ b/api/docs/v2/modules/absorbance_plate_reader.rst @@ -0,0 +1,147 @@ +:og:description: How to use the Absorbance Plate Reader Module in a Python protocol. + +.. _absorbance-plate-reader-module: + +****************************** +Absorbance Plate Reader Module +****************************** + +The Absorbance Plate Reader Module is an on-deck microplate spectrophotometer that works with the Flex robot only. The module uses light absorbance to determine sample concentrations in 96-well plates. + +The Absorbance Plate Reader is represented in code by an :py:class:`.AbsorbanceReaderContext` object, which has methods for moving the module lid with the Flex Gripper, initializing the module to read at a single wavelength or multiple wavelengths, and reading a plate. With the Python Protocol API, you can process plate reader data immediately in your protocol or export it to a CSV for post-run use. + +This page explains the actions necessary for using the Absorbance Plate Reader. These combine to form the typical reader workflow: + + 1. Close the lid with no plate inside + 2. Initialize the reader + 3. Open the lid + 4. Move a plate onto the module + 5. Close the lid + 6. Read the plate + + +Loading and Deck Slots +====================== + +The Absorbance Plate Reader can only be loaded in slots A3–D3. If you try to load it in any other slot, the API will raise an error. The module's caddy is designed such that the detection unit is in deck column 3 and the special staging area for the lid/illumination unit is in deck column 4. You can't load or move other labware on the Absorbance Plate Reader caddy in deck column 4, even while the lid is in the closed position (on top of the detection unit in deck column 3). + +The examples in this section will use an Absorbance Plate Reader Module loaded as follows:: + + pr_mod = protocol.load_module( + module_name="absorbanceReaderV1", + location="D3" + ) + +.. versionadded:: 2.21 + +Lid Control +=========== + +Flex uses the gripper to move the lid between its two positions. + + - :py:meth:`~.AbsorbanceReaderContext.open_lid()` moves the lid to the righthand side of the caddy, in deck column 4. + - :py:meth:`~.AbsorbanceReaderContext.close_lid()` moves the lid onto the detection unit, in deck column 3. + +If you call ``open_lid()`` or ``close_lid()`` and the lid is already in the corresponding position, the method will succeed immediately. You can also check the position of the lid with :py:meth:`~.AbsorbanceReaderContext.is_lid_on()`. + +You need to call ``close_lid()`` before initializing the reader, even if the reader was in the closed position at the start of the protocol. + +.. warning:: + Do not move the lid manually, during or outside of a protocol. The API does not allow manual lid movement because there is a risk of damaging the module. + +.. _absorbance-initialization: + +Initialization +============== + +Initializing the reader prepares it to read a plate later in your protocol. The :py:meth:`.AbsorbanceReaderContext.initialize` method accepts parameters for the number of readings you want to take, the wavelengths to read, and whether you want to compare the reading to a reference wavelength. In the default hardware configuration, the supported wavelengths are 450 nm (blue), 562 nm (green), 600 nm (orange), and 650 nm (red). + +The module uses these parameters immediately to perform the physical initialization. Additionally, the API preserves these values and uses them when you read the plate later in your protocol. + +Let's take a look at examples of how to combine these parameters to prepare different types of readings. The simplest reading measures one wavelength, with no reference wavelength:: + + pr_mod.initialize(mode="single", wavelengths=[450]) + +.. versionadded:: 2.21 + +Now the reader is prepared to read at 450 nm. Note that the ``wavelengths`` parameter always takes a list of integer wavelengths, even when only reading a single wavelength. + +This example can be extended by adding a reference wavelength:: + + pr_mod.initialize( + mode="single", wavelengths=[450], reference_wavelength=[562] + ) + +When configured this way, the module will read twice. In the :ref:`output data `, the values read for ``reference_wavelength`` will be subtracted from the values read for the single member of ``wavelengths``. This is useful for normalization, or to correct for background interference in wavelength measurements. + +The reader can also be initialized to take multiple measurements. When ``mode="multi"``, the ``wavelengths`` list can have up to six elements. This example will initialize the reader to read at three wavelengths:: + + pr_mod.initialize(mode="multi", wavelengths=[450, 562, 600]) + +You can't use a reference wavelength when performing multiple measurements. + + +Reading a Plate +=============== + +Use :py:meth:`.AbsorbanceReaderContext.read` to have the module read the plate, using the parameters that you specified during initialization:: + + pr_data = pr_mod.read() + +.. versionadded:: 2.21 + +The ``read()`` method returns the results in a dictionary, which the above example saves to the variable ``pr_data``. + +If you need to access this data after the conclusion of your protocol, add the ``export_filename`` parameter to instruct the API to output a CSV file, which is available in the Opentrons App by going to your Flex and viewing Recent Protocol Runs:: + + pr_data = pr_mod.read(export_filename="plate_data") + +In the above example, the API both saves the data to a variable and outputs a CSV file. If you only need the data post-run, you can omit the variable assignment. + +.. _plate-reader-data: + +Using Plate Reader Data +======================= + +There are two ways to use output data from the Absorbance Plate Reader: + +- Within your protocol as a nested dictionary object. +- Outside of your protocol, as a tabular CSV file. + +The two formats are structured differently, even though they contain the same measurement data. + +Dictionary Data +--------------- + +The dictionary object returned by ``read()`` has two nested levels. The keys at the top level are the wavelengths you provided to ``initialize()``. The keys at the second level are string names of each of the 96 wells, ``"A1"`` through ``"H12"``. The values at the second level are the measured values for each wells. These values are floating point numbers, representing the optical density (OD) of the samples in each well. OD ranges from 0.0 (low sample concentration) to 4.0 (high sample concentration). + +The nested dictionary structure allows you to access results by index later in your protocol. This example initializes a multiple read and then accesses different portions of the results:: + + # initializing and reading + pr_mod.initialize(mode="multi", wavelengths=[450, 600]) + pr_mod.open_lid() + protocol.move_labware(plate, pr_mod, use_gripper=True) + pr_mod.close_lid() + pr_data = pr_mod.read() + + # accessing results + pr_data[450]["A1"] # value for well A1 at 450 nm + pr_data[600]["H12"] # value for well H12 at 600 nm + pr_data[450] # dict of all wells at 450 nm + +You can write additional code to transform this data in any way that you need. For example, you could use a list comprehension to create a list of only the 450 nm values for column 1, ordered by well from A1 to H1:: + + [pr_data[450][w.well_name] for w in plate.columns()[0]] + +.. _absorbance-csv: + +CSV data +-------- + +The CSV exported when specifying ``export_filename`` consists of tabular data followed by additional information. Each measurement produces 9 rows in the CSV file, representing the layout of the well plate that has been read. These rows form a table with numeric labels in the first row and alphabetic labels in the first column, as you would see on physical labware. Each "cell" of the table contains the measured OD value for the well (0.0–4.0) in the corresponding position on the plate. + +Additional information, starting with one blank labware grid, is output at the end of the file. The last few lines of the file list the sample wavelengths, serial number of the module, and timestamps for when measurement started and finished. + +Each output file for your protocol is available in the Opentrons App by going to your Flex and viewing Recent Protocol Runs. After downloading the file from your Flex, you can read it with any software that reads CSV files, and you can write additional code to parse and act upon its contents. + +You can also select the output CSV as the value of a CSV runtime parameter in a subsequent protocol. When you :ref:`parse the CSV data `, make sure to set ``detect_dialect=False``, or the API will raise an error. \ No newline at end of file diff --git a/api/docs/v2/modules/setup.rst b/api/docs/v2/modules/setup.rst index c6badd82954..a0cbe18bf0e 100644 --- a/api/docs/v2/modules/setup.rst +++ b/api/docs/v2/modules/setup.rst @@ -66,7 +66,7 @@ Available Modules The first parameter of :py:meth:`.ProtocolContext.load_module` is the module's *API load name*. The load name tells your robot which module you're going to use in a protocol. The table below lists the API load names for the currently available modules. .. table:: - :widths: 4 5 2 + :widths: 4 4 2 +--------------------+-------------------------------+---------------------------+ | Module | API Load Name | Introduced in API Version | @@ -95,6 +95,9 @@ The first parameter of :py:meth:`.ProtocolContext.load_module` is the module's | Magnetic Block | ``magneticBlockV1`` | 2.15 | | GEN1 | | | +--------------------+-------------------------------+---------------------------+ + | Absorbance Plate | ``absorbanceReaderV1`` | 2.21 | + | Reader Module | | | + +--------------------+-------------------------------+---------------------------+ Some modules were added to our Python API later than others, and others span multiple hardware generations. When writing a protocol that requires a module, make sure your ``requirements`` or ``metadata`` code block specifies an :ref:`API version ` high enough to support all the module generations you want to use. @@ -124,7 +127,7 @@ Any :ref:`custom labware ` added to your Opentrons App is als Module and Labware Compatibility -------------------------------- -It's your responsibility to ensure the labware and module combinations you load together work together. The Protocol API won't raise a warning or error if you load an unusual combination, like placing a tube rack on a Thermocycler. See `What labware can I use with my modules? `_ for more information about labware/module combinations. +It's your responsibility to ensure the labware and module combinations you load together work together. The API generally won't raise a warning or error if you load an unusual combination, like placing a tube rack on a Thermocycler. The API will raise an error if you try to load a labware on an unsupported adapter. When working with custom labware and module adapters, be sure to add stacking offsets for the adapter to your custom labware definition. Additional Labware Parameters diff --git a/api/docs/v2/modules/thermocycler.rst b/api/docs/v2/modules/thermocycler.rst index 9322e0a96f0..17d57e84292 100644 --- a/api/docs/v2/modules/thermocycler.rst +++ b/api/docs/v2/modules/thermocycler.rst @@ -15,7 +15,7 @@ The examples in this section will use a Thermocycler Module GEN2 loaded as follo .. code-block:: python tc_mod = protocol.load_module(module_name="thermocyclerModuleV2") - plate = tc_mod.load_labware(name="nest_96_wellplate_100ul_pcr_full_skirt") + plate = tc_mod.load_labware(name="opentrons_96_wellplate_200ul_pcr_full_skirt") .. versionadded:: 2.13 @@ -139,6 +139,70 @@ However, this code would generate 60 lines in the protocol's run log, while exec .. versionadded:: 2.0 +Auto-sealing Lids +================= + +Starting in robot software version 8.2.0, you can use the Opentrons Tough PCR Auto-sealing Lid to reduce evaporation on the Thermocycler. The auto-sealing lids are designed for automated use with the Flex Gripper, although you can move them manually if needed. They also work with the Opentrons Flex Deck Riser adapter, which keeps lids away from the unsterilized deck and provides better access for the gripper. + +Use the following API load names for the auto-sealing lid and deck riser: + +.. list-table:: + :header-rows: 1 + + * - Labware + - API load name + * - Opentrons Tough PCR Auto-sealing Lid + - ``opentrons_tough_pcr_auto_sealing_lid`` + * - Opentrons Flex Deck Riser + - ``opentrons_flex_deck_riser`` + +Load the riser directly onto the deck with :py:meth:`.ProtocolContext.load_adapter`. Load the auto-sealing lid onto a compatible location (the deck, the riser, or another lid) with the appropriate ``load_labware()`` method. You can create a stack of up to five auto-sealing lids. If you try to stack more than five lids, the API will raise an error. + +Setting up the riser and preparing a lid to use on the Thermocycler generally consists of the following steps: + + 1. Load the riser on the deck. + 2. Load the lids onto the adapter. + 3. Load or move a PCR plate onto the Thermocycler. + 4. Move a lid onto the PCR plate. + 5. Close the Thermocycler. + +The following code sample shows how to perform these steps, using the riser and three auto-sealing lids. In a full protocol, you would likely have additional steps, such as pipetting to or from the PCR plate. + +.. code-block:: python + + # load riser + riser = protocol.load_adapter( + load_name="opentrons_flex_deck_riser", location="A2" + ) + + # load three lids + lid_1 = riser.load_labware("opentrons_tough_pcr_auto_sealing_lid") + lid_2 = lid_1.load_labware("opentrons_tough_pcr_auto_sealing_lid") + lid_3 = lid_2.load_labware("opentrons_tough_pcr_auto_sealing_lid") + + # load plate on Thermocycler + plate = protocol.load_labware( + load_name="opentrons_96_wellplate_200ul_pcr_full_skirt", location=tc_mod + ) + + # move lid to PCR plate + protocol.move_labware(labware=lid_3, new_location=plate, use_gripper=True) + + # close Thermocycler + tc_mod.close_lid() + +.. warning:: + When using the auto-sealing lids, `do not` affix a rubber automation seal to the inside of the Thermocycler lid. The Thermocycler will not close properly. + +When you're finished with a lid, use the gripper to dispose of it in either the waste chute or a trash bin:: + + tc_mod.open_lid() + protocol.move_labware(labware=lid_3, new_location=trash, use_gripper=True) + +.. versionadded:: 2.16 + :py:class:`.TrashBin` and :py:class:`.WasteChute` objects can accept lids. + +You can then move the PCR plate off of the Thermocycler. The Flex Gripper can't move a plate that has a lid on top of it. Always move the lid first, then the plate. Changes with the GEN2 Thermocycler Module ========================================= diff --git a/api/docs/v2/new_modules.rst b/api/docs/v2/new_modules.rst index 956a2bc7989..594ceca3867 100644 --- a/api/docs/v2/new_modules.rst +++ b/api/docs/v2/new_modules.rst @@ -8,6 +8,7 @@ Hardware Modules .. toctree:: modules/setup + modules/absorbance_plate_reader modules/heater_shaker modules/magnetic_block modules/magnetic_module @@ -17,13 +18,14 @@ Hardware Modules Hardware modules are powered and unpowered deck-mounted peripherals. The Flex and OT-2 are aware of deck-mounted powered modules when they're attached via a USB connection and used in an uploaded protocol. The robots do not know about unpowered modules until you use one in a protocol and upload it to the Opentrons App. -Powered modules include the Heater-Shaker Module, Magnetic Module, Temperature Module, and Thermocycler Module. The 96-well Magnetic Block is an unpowered module. +Powered modules include the Absorbance Plate Reader Module, Heater-Shaker Module, Magnetic Module, Temperature Module, and Thermocycler Module. The 96-well Magnetic Block is an unpowered module. Pages in this section of the documentation cover: - :ref:`Setting up modules and their labware `. - Working with the module contexts for each type of module. + - :ref:`Absorbance Plate Reader Module ` - :ref:`Heater-Shaker Module ` - :ref:`Magnetic Block ` - :ref:`Magnetic Module ` diff --git a/api/docs/v2/new_protocol_api.rst b/api/docs/v2/new_protocol_api.rst index a71ad5cf4a2..2ce4c39e3cc 100644 --- a/api/docs/v2/new_protocol_api.rst +++ b/api/docs/v2/new_protocol_api.rst @@ -53,29 +53,53 @@ Wells and Liquids Modules ======= +Absorbance Plate Reader +----------------------- + +.. autoclass:: opentrons.protocol_api.AbsorbanceReaderContext + :members: + :exclude-members: broker, geometry, load_labware_object, load_adapter, load_adapter_from_definition + :inherited-members: + + +Heater-Shaker +------------- + .. autoclass:: opentrons.protocol_api.HeaterShakerContext :members: :exclude-members: broker, geometry, load_labware_object :inherited-members: +Magnetic Block +-------------- + .. autoclass:: opentrons.protocol_api.MagneticBlockContext :members: :exclude-members: broker, geometry, load_labware_object :inherited-members: +Magnetic Module +--------------- + .. autoclass:: opentrons.protocol_api.MagneticModuleContext :members: :exclude-members: calibrate, broker, geometry, load_labware_object :inherited-members: +Temperature Module +------------------ + .. autoclass:: opentrons.protocol_api.TemperatureModuleContext :members: :exclude-members: start_set_temperature, await_temperature, broker, geometry, load_labware_object :inherited-members: +Thermocycler +------------ + .. autoclass:: opentrons.protocol_api.ThermocyclerContext :members: - :exclude-members: total_step_count, current_cycle_index, total_cycle_count, hold_time, ramp_rate, current_step_index, broker, geometry, load_labware_object + :exclude-members: total_step_count, current_cycle_index, total_cycle_count, hold_time, ramp_rate, current_step_index, broker, geometry, load_labware_object, load_adapter, load_adapter_from_definition :inherited-members: diff --git a/api/pytest.ini b/api/pytest.ini index a8e3bbb1933..61288b3f3c1 100644 --- a/api/pytest.ini +++ b/api/pytest.ini @@ -5,3 +5,9 @@ markers = ot3_only: Test only functions using the OT3 hardware addopts = --color=yes --strict-markers asyncio_mode = auto + +# TODO this should be looked into being removed upon updating the Decoy library. The purpose of this warning is to +# catch missing attributes, but it raises for any property referenced in a test which accounts for about ~250 warnings +# which aren't serving any useful purpose and obscure other warnings. +filterwarnings = + ignore::decoy.warnings.MissingSpecAttributeWarning diff --git a/api/src/opentrons/cli/analyze.py b/api/src/opentrons/cli/analyze.py index 8489da83d68..4c994fcf630 100644 --- a/api/src/opentrons/cli/analyze.py +++ b/api/src/opentrons/cli/analyze.py @@ -53,6 +53,7 @@ LoadedPipette, LoadedModule, Liquid, + LiquidClassRecordWithId, StateSummary, ) from opentrons.protocol_engine.protocol_engine import code_in_error_tree @@ -333,6 +334,7 @@ async def _do_analyze( wells=[], hasEverEnteredErrorRecovery=False, files=[], + liquidClasses=[], ), parameters=[], ) @@ -399,6 +401,7 @@ async def _analyze( pipettes=analysis.state_summary.pipettes, modules=analysis.state_summary.modules, liquids=analysis.state_summary.liquids, + liquidClasses=analysis.state_summary.liquidClasses, ) _call_for_output_of_kind( @@ -486,4 +489,5 @@ class AnalyzeResults(BaseModel): pipettes: List[LoadedPipette] modules: List[LoadedModule] liquids: List[Liquid] + liquidClasses: List[LiquidClassRecordWithId] errors: List[ErrorOccurrence] diff --git a/api/src/opentrons/config/defaults_ot3.py b/api/src/opentrons/config/defaults_ot3.py index 55565745d3a..53fab18392c 100644 --- a/api/src/opentrons/config/defaults_ot3.py +++ b/api/src/opentrons/config/defaults_ot3.py @@ -75,6 +75,7 @@ DEFAULT_GRIPPER_MOUNT_OFFSET: Final[Offset] = (84.55, -12.75, 93.85) DEFAULT_SAFE_HOME_DISTANCE: Final = 5 DEFAULT_CALIBRATION_AXIS_MAX_SPEED: Final = 30 +DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED: Final = 90 DEFAULT_MAX_SPEEDS: Final[ByGantryLoad[Dict[OT3AxisKind, float]]] = ByGantryLoad( high_throughput={ diff --git a/api/src/opentrons/drivers/absorbance_reader/async_byonoy.py b/api/src/opentrons/drivers/absorbance_reader/async_byonoy.py index 0460a016229..6f405c9af32 100644 --- a/api/src/opentrons/drivers/absorbance_reader/async_byonoy.py +++ b/api/src/opentrons/drivers/absorbance_reader/async_byonoy.py @@ -23,7 +23,8 @@ SN_PARSER = re.compile(r'ATTRS{serial}=="(?P.+?)"') -VERSION_PARSER = re.compile(r"Absorbance (?PV\d+\.\d+\.\d+)") +# match semver V0.0.0 (old format) or one integer (latest format) +VERSION_PARSER = re.compile(r"(?P(V\d+\.\d+\.\d+|^\d+$))") SERIAL_PARSER = re.compile(r"(?P(OPT|BYO)[A-Z]{3}[0-9]+)") @@ -156,10 +157,10 @@ async def get_device_information(self) -> Dict[str, str]: func=partial(self._interface.get_device_information, handle), ) self._raise_if_error(err.name, f"Error getting device information: {err}") - serial_match = SERIAL_PARSER.fullmatch(device_info.sn) - version_match = VERSION_PARSER.match(device_info.version) + serial_match = SERIAL_PARSER.match(device_info.sn) + version_match = VERSION_PARSER.search(device_info.version) serial = serial_match["serial"].strip() if serial_match else "OPTMAA00000" - version = version_match["version"].lower() if version_match else "v0.0.0" + version = version_match["version"].lower() if version_match else "v0" info = { "serial": serial, "version": version, diff --git a/api/src/opentrons/hardware_control/backends/flex_protocol.py b/api/src/opentrons/hardware_control/backends/flex_protocol.py index c5294938fa0..8b81d2c66ef 100644 --- a/api/src/opentrons/hardware_control/backends/flex_protocol.py +++ b/api/src/opentrons/hardware_control/backends/flex_protocol.py @@ -69,6 +69,11 @@ def update_constraints_for_calibration_with_gantry_load( ) -> None: ... + def update_constraints_for_emulsifying_pipette( + self, mount: OT3Mount, gantry_load: GantryLoad + ) -> None: + ... + def update_constraints_for_plunger_acceleration( self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad ) -> None: diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index 1251fcc4adb..87f886f1c74 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -50,6 +50,7 @@ get_system_constraints, get_system_constraints_for_calibration, get_system_constraints_for_plunger_acceleration, + get_system_constraints_for_emulsifying_pipette, ) from .tip_presence_manager import TipPresenceManager @@ -393,6 +394,18 @@ def update_constraints_for_calibration_with_gantry_load( f"Set system constraints for calibration: {self._move_manager.get_constraints()}" ) + def update_constraints_for_emulsifying_pipette( + self, mount: OT3Mount, gantry_load: GantryLoad + ) -> None: + self._move_manager.update_constraints( + get_system_constraints_for_emulsifying_pipette( + self._configuration.motion_settings, gantry_load, mount + ) + ) + log.debug( + f"Set system constraints for emulsifying pipette: {self._move_manager.get_constraints()}" + ) + def update_constraints_for_gantry_load(self, gantry_load: GantryLoad) -> None: self._move_manager.update_constraints( get_system_constraints(self._configuration.motion_settings, gantry_load) diff --git a/api/src/opentrons/hardware_control/backends/ot3simulator.py b/api/src/opentrons/hardware_control/backends/ot3simulator.py index e487f963ece..533fffe5642 100644 --- a/api/src/opentrons/hardware_control/backends/ot3simulator.py +++ b/api/src/opentrons/hardware_control/backends/ot3simulator.py @@ -234,6 +234,11 @@ def update_constraints_for_calibration_with_gantry_load( ) -> None: self._sim_gantry_load = gantry_load + def update_constraints_for_emulsifying_pipette( + self, mount: OT3Mount, gantry_load: GantryLoad + ) -> None: + pass + def update_constraints_for_plunger_acceleration( self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad ) -> None: diff --git a/api/src/opentrons/hardware_control/backends/ot3utils.py b/api/src/opentrons/hardware_control/backends/ot3utils.py index e3952cbd907..57e74537bfd 100644 --- a/api/src/opentrons/hardware_control/backends/ot3utils.py +++ b/api/src/opentrons/hardware_control/backends/ot3utils.py @@ -2,7 +2,10 @@ from typing import Dict, Iterable, List, Set, Tuple, TypeVar, cast, Sequence, Optional from typing_extensions import Literal from logging import getLogger -from opentrons.config.defaults_ot3 import DEFAULT_CALIBRATION_AXIS_MAX_SPEED +from opentrons.config.defaults_ot3 import ( + DEFAULT_CALIBRATION_AXIS_MAX_SPEED, + DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED, +) from opentrons.config.types import OT3MotionSettings, OT3CurrentSettings, GantryLoad from opentrons.hardware_control.types import ( Axis, @@ -300,6 +303,31 @@ def get_system_constraints_for_plunger_acceleration( return new_constraints +def get_system_constraints_for_emulsifying_pipette( + config: OT3MotionSettings, + gantry_load: GantryLoad, + mount: OT3Mount, +) -> "SystemConstraints[Axis]": + old_constraints = config.by_gantry_load(gantry_load) + new_constraints = {} + axis_kinds = set([k for _, v in old_constraints.items() for k in v.keys()]) + for axis_kind in axis_kinds: + for axis in Axis.of_kind(axis_kind): + if axis == Axis.of_main_tool_actuator(mount): + _max_speed = float(DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED) + else: + _max_speed = old_constraints["default_max_speed"][axis_kind] + new_constraints[axis] = AxisConstraints.build( + max_acceleration=old_constraints["acceleration"][axis_kind], + max_speed_discont=old_constraints["max_speed_discontinuity"][axis_kind], + max_direction_change_speed_discont=old_constraints[ + "direction_change_speed_discontinuity" + ][axis_kind], + max_speed=_max_speed, + ) + return new_constraints + + def _convert_to_node_id_dict( axis_pos: Coordinates[Axis, CoordinateValue], ) -> NodeIdMotionValues: @@ -642,6 +670,7 @@ def update( FirmwareGripperjawState.force_controlling_home: GripperJawState.HOMED_READY, FirmwareGripperjawState.force_controlling: GripperJawState.GRIPPING, FirmwareGripperjawState.position_controlling: GripperJawState.HOLDING, + FirmwareGripperjawState.stopped: GripperJawState.STOPPED, } diff --git a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py index 5a4d9261bfd..b9355874906 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py @@ -41,6 +41,7 @@ UlPerMmAction, PipetteName, PipetteModel, + Quirks, ) from opentrons_shared_data.pipette import ( load_data as load_pipette_data, @@ -225,6 +226,9 @@ def active_tip_settings(self) -> SupportedTipsDefinition: def push_out_volume(self) -> float: return self._active_tip_settings.default_push_out_volume + def is_high_speed_pipette(self) -> bool: + return Quirks.highSpeed in self._config.quirks + def act_as(self, name: PipetteName) -> None: """Reconfigure to act as ``name``. ``name`` must be either the actual name of the pipette, or a name in its back-compatibility diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index 491b6168e58..de2de9ae9ab 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -634,10 +634,21 @@ async def cache_pipette( self._feature_flags.use_old_aspiration_functions, ) self._pipette_handler.hardware_instruments[mount] = p + if self._pipette_handler.has_pipette(mount): + self._confirm_pipette_motion_constraints(mount) # TODO (lc 12-5-2022) Properly support backwards compatibility # when applicable return skipped + def _confirm_pipette_motion_constraints( + self, + mount: OT3Mount, + ) -> None: + if self._pipette_handler.get_pipette(mount).is_high_speed_pipette(): + self._backend.update_constraints_for_emulsifying_pipette( + mount, self.gantry_load + ) + async def cache_gripper(self, instrument_data: AttachedGripper) -> bool: """Set up gripper based on scanned information.""" grip_cal = load_gripper_calibration_offset(instrument_data.get("id")) @@ -777,11 +788,13 @@ async def _update_position_estimation( Function to update motor estimation for a set of axes """ await self._backend.update_motor_status() - if axes: - checked_axes = [ax for ax in axes if ax in Axis] - else: - checked_axes = [ax for ax in Axis] - await self._backend.update_motor_estimation(checked_axes) + + if axes is None: + axes = [ax for ax in Axis] + + axes = [ax for ax in axes if self._backend.axis_is_present(ax)] + + await self._backend.update_motor_estimation(axes) # Global actions API def pause(self, pause_type: PauseType) -> None: diff --git a/api/src/opentrons/hardware_control/protocols/position_estimator.py b/api/src/opentrons/hardware_control/protocols/position_estimator.py index 04d551020c3..fc4e1521a89 100644 --- a/api/src/opentrons/hardware_control/protocols/position_estimator.py +++ b/api/src/opentrons/hardware_control/protocols/position_estimator.py @@ -10,7 +10,7 @@ async def update_axis_position_estimations(self, axes: Sequence[Axis]) -> None: """Update the specified axes' position estimators from their encoders. This will allow these axes to make a non-home move even if they do not currently have - a position estimation (unless there is no tracked poition from the encoders, as would be + a position estimation (unless there is no tracked position from the encoders, as would be true immediately after boot). Axis encoders have less precision than their position estimators. Calling this function will @@ -19,6 +19,8 @@ async def update_axis_position_estimations(self, axes: Sequence[Axis]) -> None: This function updates only the requested axes. If other axes have bad position estimation, moves that require those axes or attempts to get the position of those axes will still fail. + Axes that are not currently available (like a plunger for a pipette that is not connected) + will be ignored. """ ... diff --git a/api/src/opentrons/hardware_control/types.py b/api/src/opentrons/hardware_control/types.py index 62265afffcc..bc32431d2a5 100644 --- a/api/src/opentrons/hardware_control/types.py +++ b/api/src/opentrons/hardware_control/types.py @@ -625,6 +625,8 @@ class GripperJawState(enum.Enum): #: the gripper is actively force-control gripping something HOLDING = enum.auto() #: the gripper is in position-control mode + STOPPED = enum.auto() + #: the gripper has been homed before but is stopped now class InstrumentProbeType(enum.Enum): diff --git a/api/src/opentrons/protocol_api/_liquid_properties.py b/api/src/opentrons/protocol_api/_liquid_properties.py index 06a23a29eb8..5aaed51edbe 100644 --- a/api/src/opentrons/protocol_api/_liquid_properties.py +++ b/api/src/opentrons/protocol_api/_liquid_properties.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from numpy import interp -from typing import Optional, Dict, Sequence, Union, Tuple +from typing import Optional, Dict, Sequence, Tuple from opentrons_shared_data.liquid_classes.liquid_class_definition import ( AspirateProperties as SharedDataAspirateProperties, @@ -23,12 +23,9 @@ class LiquidHandlingPropertyByVolume: - def __init__(self, properties_by_volume: Dict[str, float]) -> None: - self._default = properties_by_volume["default"] + def __init__(self, by_volume_property: Sequence[Tuple[float, float]]) -> None: self._properties_by_volume: Dict[float, float] = { - float(volume): value - for volume, value in properties_by_volume.items() - if volume != "default" + float(volume): value for volume, value in by_volume_property } # Volumes need to be sorted for proper interpolation of non-defined volumes, and the # corresponding values need to be in the same order for them to be interpolated correctly @@ -36,18 +33,17 @@ def __init__(self, properties_by_volume: Dict[str, float]) -> None: self._sorted_values: Tuple[float, ...] = () self._sort_volume_and_values() - @property - def default(self) -> float: - """Get the default value not associated with any volume for this property.""" - return self._default - - def as_dict(self) -> Dict[Union[float, str], float]: + def as_dict(self) -> Dict[float, float]: """Get a dictionary representation of all set volumes and values along with the default.""" - return self._properties_by_volume | {"default": self._default} + return self._properties_by_volume def get_for_volume(self, volume: float) -> float: """Get a value by volume for this property. Volumes not defined will be interpolated between set volumes.""" validated_volume = validation.ensure_positive_float(volume) + if len(self._properties_by_volume) == 0: + raise ValueError( + "No properties found for any volumes. Cannot interpolate for the given volume." + ) try: return self._properties_by_volume[validated_volume] except KeyError: @@ -66,9 +62,9 @@ def delete_for_volume(self, volume: float) -> None: """Remove an existing volume and value from the property.""" try: del self._properties_by_volume[volume] - self._sort_volume_and_values() except KeyError: raise KeyError(f"No value set for volume {volume} uL") + self._sort_volume_and_values() def _sort_volume_and_values(self) -> None: """Sort volume in increasing order along with corresponding values in matching order.""" diff --git a/api/src/opentrons/protocol_api/module_contexts.py b/api/src/opentrons/protocol_api/module_contexts.py index 7beab69c53f..8890981e32a 100644 --- a/api/src/opentrons/protocol_api/module_contexts.py +++ b/api/src/opentrons/protocol_api/module_contexts.py @@ -581,7 +581,7 @@ def set_block_temperature( individual well of the loaded labware, in µL. If not specified, the default is 25 µL. - .. note: + .. note:: If ``hold_time_minutes`` and ``hold_time_seconds`` are not specified, the Thermocycler will proceed to the next command @@ -605,7 +605,7 @@ def set_lid_temperature(self, temperature: float) -> None: :param temperature: A value between 37 and 110, representing the target temperature in °C. - .. note: + .. note:: The Thermocycler will proceed to the next command immediately after ``temperature`` has been reached. @@ -635,13 +635,13 @@ def execute_profile( individual well of the loaded labware, in µL. If not specified, the default is 25 µL. - .. note: + .. note:: Unlike with :py:meth:`set_block_temperature`, either or both of ``hold_time_minutes`` and ``hold_time_seconds`` must be defined and for each step. - .. note: + .. note:: Before API Version 2.21, Thermocycler profiles run with this command would be listed in the app as having a number of repetitions equal to @@ -991,7 +991,7 @@ class MagneticBlockContext(ModuleContext): class AbsorbanceReaderContext(ModuleContext): - """An object representing a connected Absorbance Reader Module. + """An object representing a connected Absorbance Plate Reader Module. It should not be instantiated directly; instead, it should be created through :py:meth:`.ProtocolContext.load_module`. @@ -1009,17 +1009,21 @@ def serial_number(self) -> str: @requires_version(2, 21) def close_lid(self) -> None: - """Close the lid of the Absorbance Reader.""" + """Use the Flex Gripper to close the lid of the Absorbance Plate Reader. + + You must call this method before initializing the reader, even if the reader was + in the closed position at the start of the protocol. + """ self._core.close_lid() @requires_version(2, 21) def open_lid(self) -> None: - """Open the lid of the Absorbance Reader.""" + """Use the Flex Gripper to open the lid of the Absorbance Plate Reader.""" self._core.open_lid() @requires_version(2, 21) def is_lid_on(self) -> bool: - """Return ``True`` if the Absorbance Reader's lid is currently closed.""" + """Return ``True`` if the Absorbance Plate Reader's lid is currently closed.""" return self._core.is_lid_on() @requires_version(2, 21) @@ -1029,19 +1033,28 @@ def initialize( wavelengths: List[int], reference_wavelength: Optional[int] = None, ) -> None: - """Take a zero reading on the Absorbance Plate Reader Module. + """Prepare the Absorbance Plate Reader to read a plate. + + See :ref:`absorbance-initialization` for examples. :param mode: Either ``"single"`` or ``"multi"``. - - In single measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses - one sample wavelength and an optional reference wavelength. - - In multiple measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses - a list of up to six sample wavelengths. - :param wavelengths: A list of wavelengths, in mm, to measure. - - Must contain only one item when initializing a single measurement. - - Must contain one to six items when initializing a multiple measurement. - :param reference_wavelength: An optional reference wavelength, in mm. Cannot be - used with multiple measurements. + - In single measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses + one sample wavelength and an optional reference wavelength. + - In multiple measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses + a list of up to six sample wavelengths. + :param wavelengths: A list of wavelengths, in nm, to measure. + + - In the default hardware configuration, each wavelength must be one of + ``450`` (blue), ``562`` (green), ``600`` (orange), or ``650`` (red). In + custom hardware configurations, the module may accept other integers + between 350 and 1000. + - The list must contain only one item when initializing a single measurement. + - The list can contain one to six items when initializing a multiple measurement. + :param reference_wavelength: An optional reference wavelength, in nm. If provided, + :py:meth:`.AbsorbanceReaderContext.read` will read at the reference + wavelength and then subtract the reference wavelength values from the + measurement wavelength values. Can only be used with single measurements. """ self._core.initialize( mode, wavelengths, reference_wavelength=reference_wavelength @@ -1051,16 +1064,33 @@ def initialize( def read( self, export_filename: Optional[str] = None ) -> Dict[int, Dict[str, float]]: - """Initiate read on the Absorbance Reader. + """Read a plate on the Absorbance Plate Reader. + + This method always returns a dictionary of measurement data. It optionally will + save a CSV file of the results to the Flex filesystem, which you can access from + the Recent Protocol Runs screen in the Opentrons App. These files are `only` saved + if you specify ``export_filename``. + + In simulation, the values for each well key in the dictionary are set to zero, and + no files are written. + + .. note:: + + Avoid divide-by-zero errors when simulating and using the results of this + method later in the protocol. If you divide by any of the measurement + values, use :py:meth:`.ProtocolContext.is_simulating` to use alternate dummy + data or skip the division step. - Returns a dictionary of wavelengths to dictionary of values ordered by well name. + :param export_filename: An optional file basename. If provided, this method + will write a CSV file for each measurement in the read operation. File + names will use the value of this parameter, the measurement wavelength + supplied in :py:meth:`~.AbsorbanceReaderContext.initialize`, and a + ``.csv`` extension. For example, when reading at wavelengths 450 and 562 + with ``export_filename="my_data"``, there will be two output files: + ``my_data_450.csv`` and ``my_data_562.csv``. - :param export_filename: Optional, if a filename is provided a CSV file will be saved - as a result of the read action containing measurement data. The filename will - be modified to include the wavelength used during measurement. If multiple - measurements are taken, then a file will be generated for each wavelength provided. + See :ref:`absorbance-csv` for information on working with these CSV files. - Example: If `export_filename="my_data"` and wavelengths 450 and 531 are used during - measurement, the output files will be "my_data_450.csv" and "my_data_531.csv". + :returns: A dictionary of wavelengths to dictionary of values ordered by well name. """ return self._core.read(filename=export_filename) diff --git a/api/src/opentrons/protocol_engine/__init__.py b/api/src/opentrons/protocol_engine/__init__.py index 25599189916..7efaef7199d 100644 --- a/api/src/opentrons/protocol_engine/__init__.py +++ b/api/src/opentrons/protocol_engine/__init__.py @@ -57,6 +57,8 @@ ModuleModel, ModuleDefinition, Liquid, + LiquidClassRecord, + LiquidClassRecordWithId, AllNozzleLayoutConfiguration, SingleNozzleLayoutConfiguration, RowNozzleLayoutConfiguration, @@ -122,6 +124,8 @@ "ModuleModel", "ModuleDefinition", "Liquid", + "LiquidClassRecord", + "LiquidClassRecordWithId", "AllNozzleLayoutConfiguration", "SingleNozzleLayoutConfiguration", "RowNozzleLayoutConfiguration", diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/initialize.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/initialize.py index 4b28154ed17..458225ad1bb 100644 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/initialize.py +++ b/api/src/opentrons/protocol_engine/commands/absorbance_reader/initialize.py @@ -10,6 +10,7 @@ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ...errors.error_occurrence import ErrorOccurrence +from ...errors import InvalidWavelengthError if TYPE_CHECKING: from opentrons.protocol_engine.state.state import StateView @@ -69,30 +70,41 @@ async def execute(self, params: InitializeParams) -> SuccessData[InitializeResul unsupported_wavelengths = sample_wavelengths.difference( supported_wavelengths ) + sample_wl_str = ", ".join([str(w) + "nm" for w in sample_wavelengths]) + supported_wl_str = ", ".join([str(w) + "nm" for w in supported_wavelengths]) + unsupported_wl_str = ", ".join( + [str(w) + "nm" for w in unsupported_wavelengths] + ) if unsupported_wavelengths: - raise ValueError(f"Unsupported wavelengths: {unsupported_wavelengths}") + raise InvalidWavelengthError( + f"Unsupported wavelengths: {unsupported_wl_str}. " + f" Use one of {supported_wl_str} instead." + ) if params.measureMode == "single": if sample_wavelengths_len != 1: raise ValueError( - f"single requires one sample wavelength, provided {sample_wavelengths}" + f"Measure mode `single` requires one sample wavelength," + f" {sample_wl_str} provided instead." ) if ( reference_wavelength is not None and reference_wavelength not in supported_wavelengths ): - raise ValueError( - f"Reference wavelength {reference_wavelength} not supported {supported_wavelengths}" + raise InvalidWavelengthError( + f"Reference wavelength {reference_wavelength}nm is not supported." + f" Use one of {supported_wl_str} instead." ) if params.measureMode == "multi": if sample_wavelengths_len < 1 or sample_wavelengths_len > 6: raise ValueError( - f"multi requires 1-6 sample wavelengths, provided {sample_wavelengths}" + f"Measure mode `multi` requires 1-6 sample wavelengths," + f" {sample_wl_str} provided instead." ) if reference_wavelength is not None: - raise RuntimeError( - "Reference wavelength cannot be used with multi mode." + raise ValueError( + "Reference wavelength cannot be used with Measure mode `multi`." ) await abs_reader.set_sample_wavelength( diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py index 8743fd1383b..1ca848858b6 100644 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py +++ b/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py @@ -80,6 +80,10 @@ async def execute( # noqa: C901 raise CannotPerformModuleAction( "Cannot perform Read action on Absorbance Reader without calling `.initialize(...)` first." ) + if abs_reader_substate.is_lid_on is False: + raise CannotPerformModuleAction( + "Cannot perform Read action on Absorbance Reader with the lid open. Try calling `.close_lid()` first." + ) # TODO: we need to return a file ID and increase the file count even when a moduel is not attached if ( diff --git a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py index 7fc7b62dc45..1f89c9c5d74 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py @@ -84,7 +84,6 @@ async def execute(self, params: AspirateInPlaceParams) -> _ExecuteReturn: ready_to_aspirate = self._pipetting.get_is_ready_to_aspirate( pipette_id=params.pipetteId, ) - if not ready_to_aspirate: raise PipetteNotReadyToAspirateError( "Pipette cannot aspirate in place because of a previous blow out." diff --git a/api/src/opentrons/protocol_engine/commands/command.py b/api/src/opentrons/protocol_engine/commands/command.py index e47ae9f3a37..c009f314afb 100644 --- a/api/src/opentrons/protocol_engine/commands/command.py +++ b/api/src/opentrons/protocol_engine/commands/command.py @@ -184,7 +184,9 @@ class BaseCommand( ) error: Union[ _ErrorT, - # ErrorOccurrence here is for undefined errors not captured by _ErrorT. + # ErrorOccurrence here is a catch-all for undefined errors not captured by + # _ErrorT, or defined errors that don't parse into _ErrorT because, for example, + # they are from an older software version that was missing some fields. ErrorOccurrence, None, ] = Field( diff --git a/api/src/opentrons/protocol_engine/commands/drop_tip.py b/api/src/opentrons/protocol_engine/commands/drop_tip.py index 18c90360c42..4faee3d5e2f 100644 --- a/api/src/opentrons/protocol_engine/commands/drop_tip.py +++ b/api/src/opentrons/protocol_engine/commands/drop_tip.py @@ -147,6 +147,13 @@ async def execute(self, params: DropTipParams) -> _ExecuteReturn: error=exception, ) ], + errorInfo={ + "retryLocation": ( + move_result.public.position.x, + move_result.public.position.y, + move_result.public.position.z, + ) + }, ) return DefinedErrorData( public=error, @@ -168,7 +175,11 @@ async def execute(self, params: DropTipParams) -> _ExecuteReturn: ) -class DropTip(BaseCommand[DropTipParams, DropTipResult, ErrorOccurrence]): +class DropTip( + BaseCommand[ + DropTipParams, DropTipResult, TipPhysicallyAttachedError | StallOrCollisionError + ] +): """Drop tip command model.""" commandType: DropTipCommandType = "dropTip" diff --git a/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py b/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py index 0f98b32ff58..8687382b53f 100644 --- a/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py @@ -18,7 +18,7 @@ from ..state import update_types if TYPE_CHECKING: - from ..execution import TipHandler + from ..execution import TipHandler, GantryMover DropTipInPlaceCommandType = Literal["dropTipInPlace"] @@ -57,14 +57,19 @@ def __init__( self, tip_handler: TipHandler, model_utils: ModelUtils, + gantry_mover: GantryMover, **kwargs: object, ) -> None: self._tip_handler = tip_handler self._model_utils = model_utils + self._gantry_mover = gantry_mover async def execute(self, params: DropTipInPlaceParams) -> _ExecuteReturn: """Drop a tip using the requested pipette.""" state_update = update_types.StateUpdate() + + retry_location = await self._gantry_mover.get_position(params.pipetteId) + try: await self._tip_handler.drop_tip( pipette_id=params.pipetteId, home_after=params.homeAfter @@ -85,6 +90,7 @@ async def execute(self, params: DropTipInPlaceParams) -> _ExecuteReturn: error=exception, ) ], + errorInfo={"retryLocation": retry_location}, ) return DefinedErrorData( public=error, @@ -100,7 +106,7 @@ async def execute(self, params: DropTipInPlaceParams) -> _ExecuteReturn: class DropTipInPlace( - BaseCommand[DropTipInPlaceParams, DropTipInPlaceResult, ErrorOccurrence] + BaseCommand[DropTipInPlaceParams, DropTipInPlaceResult, TipPhysicallyAttachedError] ): """Drop tip in place command model.""" diff --git a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py index 101d9f2e02c..af8723a5bba 100644 --- a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py +++ b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py @@ -87,7 +87,7 @@ class TipPhysicallyMissingError(ErrorOccurrence): isDefined: bool = True errorType: Literal["tipPhysicallyMissing"] = "tipPhysicallyMissing" errorCode: str = ErrorCodes.TIP_PICKUP_FAILED.value.code - detail: str = "No tip detected." + detail: str = "No Tip Detected" _ExecuteReturn = Union[ diff --git a/api/src/opentrons/protocol_engine/commands/pipetting_common.py b/api/src/opentrons/protocol_engine/commands/pipetting_common.py index ee69a3e3764..0292b51eee1 100644 --- a/api/src/opentrons/protocol_engine/commands/pipetting_common.py +++ b/api/src/opentrons/protocol_engine/commands/pipetting_common.py @@ -72,7 +72,12 @@ class BaseLiquidHandlingResult(BaseModel): class ErrorLocationInfo(TypedDict): - """Holds a retry location for in-place error recovery.""" + """Holds a retry location for in-place error recovery. + + This is appropriate to pass to a `moveToCoordinates` command, + assuming the pipette has not been configured with a different nozzle layout + in the meantime. + """ retryLocation: Tuple[float, float, float] @@ -126,6 +131,8 @@ class TipPhysicallyAttachedError(ErrorOccurrence): errorCode: str = ErrorCodes.TIP_DROP_FAILED.value.code detail: str = ErrorCodes.TIP_DROP_FAILED.value.detail + errorInfo: ErrorLocationInfo + async def prepare_for_aspirate( pipette_id: str, diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py index aa11555954d..c69cea29243 100644 --- a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +++ b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py @@ -129,9 +129,14 @@ async def execute( module.id ) - # NOTE: When the estop is pressed, the gantry loses position, - # so the robot needs to home x, y to sync. - await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G, Axis.X, Axis.Y]) + # NOTE: When the estop is pressed, the gantry loses position, lets use + # the encoders to sync position. + # Ideally, we'd do a full home, but this command is used when + # the gripper is holding the plate reader, and a full home would + # bang it into the right window. + await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G]) + await ot3api.engage_axes([Axis.X, Axis.Y]) + await ot3api.update_axis_position_estimations([Axis.X, Axis.Y]) # Place the labware down await self._start_movement(ot3api, definition, location, drop_offset) diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/update_position_estimators.py b/api/src/opentrons/protocol_engine/commands/unsafe/update_position_estimators.py index cf5454db332..ff06b6c22ed 100644 --- a/api/src/opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +++ b/api/src/opentrons/protocol_engine/commands/unsafe/update_position_estimators.py @@ -23,7 +23,11 @@ class UpdatePositionEstimatorsParams(BaseModel): """Payload required for an UpdatePositionEstimators command.""" axes: List[MotorAxis] = Field( - ..., description="The axes for which to update the position estimators." + ..., + description=( + "The axes for which to update the position estimators." + " Any axes that are not physically present will be ignored." + ), ) diff --git a/api/src/opentrons/protocol_engine/errors/__init__.py b/api/src/opentrons/protocol_engine/errors/__init__.py index 2706a4bc862..8148ce132e6 100644 --- a/api/src/opentrons/protocol_engine/errors/__init__.py +++ b/api/src/opentrons/protocol_engine/errors/__init__.py @@ -55,6 +55,7 @@ InvalidTargetTemperatureError, InvalidBlockVolumeError, InvalidHoldTimeError, + InvalidWavelengthError, CannotPerformModuleAction, PauseNotAllowedError, ResumeFromRecoveryNotAllowedError, @@ -141,6 +142,7 @@ "InvalidBlockVolumeError", "InvalidHoldTimeError", "InvalidLiquidError", + "InvalidWavelengthError", "CannotPerformModuleAction", "ResumeFromRecoveryNotAllowedError", "PauseNotAllowedError", diff --git a/api/src/opentrons/protocol_engine/errors/error_occurrence.py b/api/src/opentrons/protocol_engine/errors/error_occurrence.py index 02bcfb38b62..4141befe9b8 100644 --- a/api/src/opentrons/protocol_engine/errors/error_occurrence.py +++ b/api/src/opentrons/protocol_engine/errors/error_occurrence.py @@ -12,8 +12,6 @@ log = getLogger(__name__) -# TODO(mc, 2021-11-12): flesh this model out with structured error data -# for each error type so client may produce better error messages class ErrorOccurrence(BaseModel): """An occurrence of a specific error during protocol execution.""" @@ -44,8 +42,15 @@ def from_failed( id: str = Field(..., description="Unique identifier of this error occurrence.") createdAt: datetime = Field(..., description="When the error occurred.") + # Our Python should probably always set this to False--if we want it to be True, + # we should probably be using a more specific subclass of ErrorOccurrence anyway. + # However, we can't make this Literal[False], because we want this class to be able + # to act as a catch-all for parsing defined errors that might be missing some + # `errorInfo` fields because they were serialized by older software. isDefined: bool = Field( - default=False, # default=False for database backwards compatibility. + # default=False for database backwards compatibility, so we can parse objects + # serialized before isDefined existed. + default=False, description=dedent( """\ Whether this error is *defined.* diff --git a/api/src/opentrons/protocol_engine/errors/exceptions.py b/api/src/opentrons/protocol_engine/errors/exceptions.py index 7c16156b4bb..563a1fb816d 100644 --- a/api/src/opentrons/protocol_engine/errors/exceptions.py +++ b/api/src/opentrons/protocol_engine/errors/exceptions.py @@ -786,6 +786,19 @@ def __init__( super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) +class InvalidWavelengthError(ProtocolEngineError): + """Raised when attempting to set an invalid absorbance wavelength.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build a InvalidWavelengthError.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + class InvalidHoldTimeError(ProtocolEngineError): """An error raised when attempting to set an invalid temperature hold time.""" diff --git a/api/src/opentrons/protocol_engine/state/state.py b/api/src/opentrons/protocol_engine/state/state.py index 47b15e4eb3b..58e977cc2f4 100644 --- a/api/src/opentrons/protocol_engine/state/state.py +++ b/api/src/opentrons/protocol_engine/state/state.py @@ -9,7 +9,7 @@ from opentrons_shared_data.robot.types import RobotDefinition from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy -from opentrons.protocol_engine.types import ModuleOffsetData +from opentrons.protocol_engine.types import LiquidClassRecordWithId, ModuleOffsetData from opentrons.util.change_notifier import ChangeNotifier from ..resources import DeckFixedLabware @@ -156,7 +156,12 @@ def get_summary(self) -> StateSummary: wells=self._wells.get_all(), hasEverEnteredErrorRecovery=self._commands.get_has_entered_recovery_mode(), files=self._state.files.file_ids, - # TODO(dc): Do we want to just dump all the liquid classes into the summary? + liquidClasses=[ + LiquidClassRecordWithId( + liquidClassId=liquid_class_id, **dict(liquid_class_record) + ) + for liquid_class_id, liquid_class_record in self._liquid_classes.get_all().items() + ], ) diff --git a/api/src/opentrons/protocol_engine/state/state_summary.py b/api/src/opentrons/protocol_engine/state/state_summary.py index 7e47ccbbb37..d6b18613071 100644 --- a/api/src/opentrons/protocol_engine/state/state_summary.py +++ b/api/src/opentrons/protocol_engine/state/state_summary.py @@ -11,6 +11,7 @@ LoadedModule, LoadedPipette, Liquid, + LiquidClassRecordWithId, WellInfoSummary, ) @@ -32,3 +33,4 @@ class StateSummary(BaseModel): liquids: List[Liquid] = Field(default_factory=list) wells: List[WellInfoSummary] = Field(default_factory=list) files: List[str] = Field(default_factory=list) + liquidClasses: List[LiquidClassRecordWithId] = Field(default_factory=list) diff --git a/api/src/opentrons/protocol_engine/types.py b/api/src/opentrons/protocol_engine/types.py index 1a11a99df86..2a0bbf78c28 100644 --- a/api/src/opentrons/protocol_engine/types.py +++ b/api/src/opentrons/protocol_engine/types.py @@ -887,6 +887,15 @@ def dict_to_tuple(d: dict[str, Any]) -> tuple[tuple[str, Any], ...]: return hash(dict_to_tuple(self.dict())) +class LiquidClassRecordWithId(LiquidClassRecord, frozen=True): + """A LiquidClassRecord with its ID, for use in summary lists.""" + + liquidClassId: str = Field( + ..., + description="Unique identifier for this liquid class.", + ) + + class SpeedRange(NamedTuple): """Minimum and maximum allowed speeds for a shaking module.""" diff --git a/api/src/opentrons/protocol_runner/run_orchestrator.py b/api/src/opentrons/protocol_runner/run_orchestrator.py index 8339b00f930..5568639f246 100644 --- a/api/src/opentrons/protocol_runner/run_orchestrator.py +++ b/api/src/opentrons/protocol_runner/run_orchestrator.py @@ -419,6 +419,21 @@ def get_nozzle_maps(self) -> Mapping[str, NozzleMapInterface]: """Get current nozzle maps keyed by pipette id.""" return self._protocol_engine.state_view.tips.get_pipette_nozzle_maps() + def get_tip_attached(self) -> Dict[str, bool]: + """Get current tip state keyed by pipette id.""" + + def has_tip_attached(pipette_id: str) -> bool: + return ( + self._protocol_engine.state_view.pipettes.get_attached_tip(pipette_id) + is not None + ) + + pipette_ids = ( + pipette.id + for pipette in self._protocol_engine.state_view.pipettes.get_all() + ) + return {pipette_id: has_tip_attached(pipette_id) for pipette_id in pipette_ids} + def set_error_recovery_policy(self, policy: ErrorRecoveryPolicy) -> None: """Create error recovery policy for the run.""" self._protocol_engine.set_error_recovery_policy(policy) diff --git a/api/src/opentrons/protocols/parameters/csv_parameter_interface.py b/api/src/opentrons/protocols/parameters/csv_parameter_interface.py index 6da9a0f7aaf..ff460b48f21 100644 --- a/api/src/opentrons/protocols/parameters/csv_parameter_interface.py +++ b/api/src/opentrons/protocols/parameters/csv_parameter_interface.py @@ -60,7 +60,9 @@ def parse_as_csv( as appropriate. :param detect_dialect: If ``True``, examine the file and try to assign it a - :py:class:`csv.Dialect` to improve parsing behavior. + :py:class:`csv.Dialect` to improve parsing behavior. Set this to ``False`` + when using the file output of :py:meth:`.AbsorbanceReaderContext.read` as + a runtime parameter. :param kwargs: For advanced CSV handling, you can pass any of the `formatting parameters `_ accepted by :py:func:`csv.reader` from the Python standard library. diff --git a/api/tests/opentrons/conftest.py b/api/tests/opentrons/conftest.py index cf8fdd0e97c..e8ca2b059ff 100755 --- a/api/tests/opentrons/conftest.py +++ b/api/tests/opentrons/conftest.py @@ -804,10 +804,10 @@ def minimal_liquid_class_def2() -> LiquidClassSchemaV1: namespace="test-fixture-2", byPipette=[ ByPipetteSetting( - pipetteModel="p20_single_gen2", + pipetteModel="flex_1channel_50", byTipType=[ ByTipTypeSetting( - tiprack="opentrons_96_tiprack_20ul", + tiprack="opentrons_flex_96_tiprack_50ul", aspirate=AspirateProperties( submerge=Submerge( positionReference=PositionReference.LIQUID_MENISCUS, @@ -821,13 +821,13 @@ def minimal_liquid_class_def2() -> LiquidClassSchemaV1: positionReference=PositionReference.WELL_TOP, offset=Coordinate(x=0, y=0, z=5), speed=100, - airGapByVolume={"default": 2, "5": 3, "10": 4}, + airGapByVolume=[(5.0, 3.0), (10.0, 4.0)], touchTip=TouchTipProperties(enable=False), delay=DelayProperties(enable=False), ), positionReference=PositionReference.WELL_BOTTOM, offset=Coordinate(x=0, y=0, z=-5), - flowRateByVolume={"default": 50, "10": 40, "20": 30}, + flowRateByVolume=[(10.0, 40.0), (20.0, 30.0)], preWet=True, mix=MixProperties(enable=False), delay=DelayProperties( @@ -845,16 +845,16 @@ def minimal_liquid_class_def2() -> LiquidClassSchemaV1: positionReference=PositionReference.WELL_TOP, offset=Coordinate(x=0, y=0, z=5), speed=100, - airGapByVolume={"default": 2, "5": 3, "10": 4}, + airGapByVolume=[(5.0, 3.0), (10.0, 4.0)], blowout=BlowoutProperties(enable=False), touchTip=TouchTipProperties(enable=False), delay=DelayProperties(enable=False), ), positionReference=PositionReference.WELL_BOTTOM, offset=Coordinate(x=0, y=0, z=-5), - flowRateByVolume={"default": 50, "10": 40, "20": 30}, + flowRateByVolume=[(10.0, 40.0), (20.0, 30.0)], mix=MixProperties(enable=False), - pushOutByVolume={"default": 5, "10": 7, "20": 10}, + pushOutByVolume=[(10.0, 7.0), (20.0, 10.0)], delay=DelayProperties(enable=False), ), multiDispense=None, diff --git a/api/tests/opentrons/drivers/absorbance_reader/test_driver.py b/api/tests/opentrons/drivers/absorbance_reader/test_driver.py index 58552695f44..b4db8d604b2 100644 --- a/api/tests/opentrons/drivers/absorbance_reader/test_driver.py +++ b/api/tests/opentrons/drivers/absorbance_reader/test_driver.py @@ -124,6 +124,36 @@ async def test_driver_get_device_info( mock_interface.get_device_information.assert_called_once() mock_interface.reset_mock() + # Test Device info with updated version format + DEVICE_INFO.sn = "OPTMAA00034" + DEVICE_INFO.version = "8" + + mock_interface.get_device_information.return_value = ( + MockErrorCode.NO_ERROR, + DEVICE_INFO, + ) + + info = await connected_driver.get_device_info() + + assert info == {"serial": "OPTMAA00034", "model": "ABS96", "version": "8"} + mock_interface.get_device_information.assert_called_once() + mock_interface.reset_mock() + + # Test Device info with invalid version format + DEVICE_INFO.sn = "OPTMAA00034" + DEVICE_INFO.version = "asd" + + mock_interface.get_device_information.return_value = ( + MockErrorCode.NO_ERROR, + DEVICE_INFO, + ) + + info = await connected_driver.get_device_info() + + assert info == {"serial": "OPTMAA00034", "model": "ABS96", "version": "v0"} + mock_interface.get_device_information.assert_called_once() + mock_interface.reset_mock() + @pytest.mark.parametrize( "parts_aligned, module_status", diff --git a/api/tests/opentrons/hardware_control/backends/test_ot3_utils.py b/api/tests/opentrons/hardware_control/backends/test_ot3_utils.py index 2e650a2c246..d7125cfb027 100644 --- a/api/tests/opentrons/hardware_control/backends/test_ot3_utils.py +++ b/api/tests/opentrons/hardware_control/backends/test_ot3_utils.py @@ -3,7 +3,7 @@ from opentrons_hardware.hardware_control.motion_planning import Move from opentrons.hardware_control.backends import ot3utils from opentrons_hardware.firmware_bindings.constants import NodeId -from opentrons.hardware_control.types import Axis, OT3Mount +from opentrons.hardware_control.types import Axis, OT3Mount, OT3AxisKind from numpy import float64 as f64 from opentrons.config import defaults_ot3, types as conf_types @@ -95,6 +95,22 @@ def test_get_system_contraints_for_plunger() -> None: assert updated_contraints[axis].max_acceleration == set_acceleration +@pytest.mark.parametrize(["mount"], [[OT3Mount.LEFT], [OT3Mount.RIGHT]]) +def test_get_system_constraints_for_emulsifying_pipette(mount: OT3Mount) -> None: + set_max_speed = 90 + config = defaults_ot3.build_with_defaults({}) + pipette_ax = Axis.of_main_tool_actuator(mount) + default_pip_max_speed = config.motion_settings.default_max_speed[ + conf_types.GantryLoad.LOW_THROUGHPUT + ][OT3AxisKind.P] + updated_constraints = ot3utils.get_system_constraints_for_emulsifying_pipette( + config.motion_settings, conf_types.GantryLoad.LOW_THROUGHPUT, mount + ) + other_pipette = list(set(Axis.pipette_axes()) - {pipette_ax})[0] + assert updated_constraints[pipette_ax].max_speed == set_max_speed + assert updated_constraints[other_pipette].max_speed == default_pip_max_speed + + @pytest.mark.parametrize( ["moving", "expected"], [ diff --git a/api/tests/opentrons/hardware_control/test_ot3_api.py b/api/tests/opentrons/hardware_control/test_ot3_api.py index 4c7247e9ec7..2fd3fb4377c 100644 --- a/api/tests/opentrons/hardware_control/test_ot3_api.py +++ b/api/tests/opentrons/hardware_control/test_ot3_api.py @@ -2038,23 +2038,36 @@ def set_mock_plunger_configs() -> None: @pytest.mark.parametrize( - "axes", - [[Axis.X], [Axis.X, Axis.Y], [Axis.X, Axis.Y, Axis.P_L], None], + ("axes_in", "axes_present", "expected_axes"), + [ + ([Axis.X, Axis.Y], [Axis.X, Axis.Y], [Axis.X, Axis.Y]), + ([Axis.X, Axis.Y], [Axis.Y, Axis.Z_L], [Axis.Y]), + (None, list(Axis), list(Axis)), + (None, [Axis.Y, Axis.Z_L], [Axis.Y, Axis.Z_L]), + ], ) async def test_update_position_estimation( ot3_hardware: ThreadManager[OT3API], hardware_backend: OT3Simulator, - axes: List[Axis], + axes_in: List[Axis], + axes_present: List[Axis], + expected_axes: List[Axis], ) -> None: + def _axis_is_present(axis: Axis) -> bool: + return axis in axes_present + with patch.object( hardware_backend, "update_motor_estimation", AsyncMock(spec=hardware_backend.update_motor_estimation), - ) as mock_update: - await ot3_hardware._update_position_estimation(axes) - if axes is None: - axes = [ax for ax in Axis] - mock_update.assert_called_once_with(axes) + ) as mock_update, patch.object( + hardware_backend, + "axis_is_present", + Mock(spec=hardware_backend.axis_is_present), + ) as mock_axis_is_present: + mock_axis_is_present.side_effect = _axis_is_present + await ot3_hardware._update_position_estimation(axes_in) + mock_update.assert_called_once_with(expected_axes) async def test_refresh_positions( diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 1b8445ed7b2..8282f660a44 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -1501,6 +1501,7 @@ def test_mix_no_lpd( mock_well = decoy.mock(cls=Well) bottom_location = Location(point=Point(1, 2, 3), labware=mock_well) + top_location = Location(point=Point(3, 2, 1), labware=None) input_location = Location(point=Point(2, 2, 2), labware=None) last_location = Location(point=Point(9, 9, 9), labware=None) @@ -1516,6 +1517,7 @@ def test_mix_no_lpd( mock_validation.validate_location(location=None, last_location=last_location) ).then_return(WellTarget(well=mock_well, location=None, in_place=False)) decoy.when(mock_well.bottom(z=1.0)).then_return(bottom_location) + decoy.when(mock_well.top()).then_return(top_location) decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) decoy.when(mock_instrument_core.get_dispense_flow_rate(1.23)).then_return(5.67) decoy.when(mock_instrument_core.has_tip()).then_return(True) @@ -1523,19 +1525,63 @@ def test_mix_no_lpd( subject.mix(repetitions=10, volume=10.0, location=input_location, rate=1.23) decoy.verify( - mock_instrument_core.aspirate(), # type: ignore[call-arg] - ignore_extra_args=True, - times=10, - ) - decoy.verify( - mock_instrument_core.dispense(), # type: ignore[call-arg] - ignore_extra_args=True, + mock_instrument_core.aspirate( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + ), times=10, ) + # Slight differences in dispense push-out logic for 2.14 and 2.15 api levels + if subject.api_version < APIVersion(2, 16): + decoy.verify( + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + None, + ), + times=10, + ) + else: + decoy.verify( + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + 0.0, + None, + ), + times=9, + ) + decoy.verify( + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + None, + ), + times=1, + ) + decoy.verify( - mock_instrument_core.liquid_probe_with_recovery(), # type: ignore[call-arg] - ignore_extra_args=True, + mock_instrument_core.liquid_probe_with_recovery(mock_well._core, top_location), times=0, ) @@ -1551,6 +1597,7 @@ def test_mix_with_lpd( """It should aspirate/dispense to a well several times and do 1 lpd.""" mock_well = decoy.mock(cls=Well) bottom_location = Location(point=Point(1, 2, 3), labware=mock_well) + top_location = Location(point=Point(3, 2, 1), labware=None) input_location = Location(point=Point(2, 2, 2), labware=None) last_location = Location(point=Point(9, 9, 9), labware=None) @@ -1566,6 +1613,7 @@ def test_mix_with_lpd( mock_validation.validate_location(location=None, last_location=last_location) ).then_return(WellTarget(well=mock_well, location=None, in_place=False)) decoy.when(mock_well.bottom(z=1.0)).then_return(bottom_location) + decoy.when(mock_well.top()).then_return(top_location) decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) decoy.when(mock_instrument_core.get_dispense_flow_rate(1.23)).then_return(5.67) decoy.when(mock_instrument_core.has_tip()).then_return(True) @@ -1577,19 +1625,45 @@ def test_mix_with_lpd( subject.liquid_presence_detection = True subject.mix(repetitions=10, volume=10.0, location=input_location, rate=1.23) decoy.verify( - mock_instrument_core.aspirate(), # type: ignore[call-arg] - ignore_extra_args=True, + mock_instrument_core.aspirate( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + ), times=10, ) decoy.verify( - mock_instrument_core.dispense(), # type: ignore[call-arg] - ignore_extra_args=True, - times=10, + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + 0.0, + None, + ), + times=9, + ) + decoy.verify( + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + None, + ), + times=1, ) - decoy.verify( - mock_instrument_core.liquid_probe_with_recovery(), # type: ignore[call-arg] - ignore_extra_args=True, + mock_instrument_core.liquid_probe_with_recovery(mock_well._core, top_location), times=1, ) diff --git a/api/tests/opentrons/protocol_api/test_liquid_class.py b/api/tests/opentrons/protocol_api/test_liquid_class.py index 463889b3da6..7118080eda0 100644 --- a/api/tests/opentrons/protocol_api/test_liquid_class.py +++ b/api/tests/opentrons/protocol_api/test_liquid_class.py @@ -21,9 +21,8 @@ def test_get_for_pipette_and_tip( ) -> None: """It should get the properties for the specified pipette and tip.""" liq_class = LiquidClass.create(minimal_liquid_class_def2) - result = liq_class.get_for("p20_single_gen2", "opentrons_96_tiprack_20ul") + result = liq_class.get_for("flex_1channel_50", "opentrons_flex_96_tiprack_50ul") assert result.aspirate.flow_rate_by_volume.as_dict() == { - "default": 50.0, 10.0: 40.0, 20.0: 30.0, } @@ -36,7 +35,7 @@ def test_get_for_raises_for_incorrect_pipette_or_tip( liq_class = LiquidClass.create(minimal_liquid_class_def2) with pytest.raises(ValueError): - liq_class.get_for("p20_single_gen2", "no_such_tiprack") + liq_class.get_for("flex_1channel_50", "no_such_tiprack") with pytest.raises(ValueError): - liq_class.get_for("p300_single", "opentrons_96_tiprack_20ul") + liq_class.get_for("no_such_pipette", "opentrons_flex_96_tiprack_50ul") diff --git a/api/tests/opentrons/protocol_api/test_liquid_class_properties.py b/api/tests/opentrons/protocol_api/test_liquid_class_properties.py index e1e9b540149..f7033afb5be 100644 --- a/api/tests/opentrons/protocol_api/test_liquid_class_properties.py +++ b/api/tests/opentrons/protocol_api/test_liquid_class_properties.py @@ -16,7 +16,7 @@ def test_build_aspirate_settings() -> None: """It should convert the shared data aspirate settings to the PAPI type.""" - fixture_data = load_shared_data("liquid-class/fixtures/fixture_glycerol50.json") + fixture_data = load_shared_data("liquid-class/fixtures/1/fixture_glycerol50.json") liquid_class_model = LiquidClassSchemaV1.parse_raw(fixture_data) aspirate_data = liquid_class_model.byPipette[0].byTipType[0].aspirate @@ -32,7 +32,6 @@ def test_build_aspirate_settings() -> None: assert aspirate_properties.retract.offset == Coordinate(x=0, y=0, z=5) assert aspirate_properties.retract.speed == 100 assert aspirate_properties.retract.air_gap_by_volume.as_dict() == { - "default": 2.0, 5.0: 3.0, 10.0: 4.0, } @@ -45,7 +44,7 @@ def test_build_aspirate_settings() -> None: assert aspirate_properties.position_reference.value == "well-bottom" assert aspirate_properties.offset == Coordinate(x=0, y=0, z=-5) - assert aspirate_properties.flow_rate_by_volume.as_dict() == {"default": 50.0} + assert aspirate_properties.flow_rate_by_volume.as_dict() == {10: 50.0} assert aspirate_properties.pre_wet is True assert aspirate_properties.mix.enabled is True assert aspirate_properties.mix.repetitions == 3 @@ -56,7 +55,7 @@ def test_build_aspirate_settings() -> None: def test_build_single_dispense_settings() -> None: """It should convert the shared data single dispense settings to the PAPI type.""" - fixture_data = load_shared_data("liquid-class/fixtures/fixture_glycerol50.json") + fixture_data = load_shared_data("liquid-class/fixtures/1/fixture_glycerol50.json") liquid_class_model = LiquidClassSchemaV1.parse_raw(fixture_data) single_dispense_data = liquid_class_model.byPipette[0].byTipType[0].singleDispense @@ -75,7 +74,6 @@ def test_build_single_dispense_settings() -> None: assert single_dispense_properties.retract.offset == Coordinate(x=0, y=0, z=5) assert single_dispense_properties.retract.speed == 100 assert single_dispense_properties.retract.air_gap_by_volume.as_dict() == { - "default": 2.0, 5.0: 3.0, 10.0: 4.0, } @@ -93,7 +91,6 @@ def test_build_single_dispense_settings() -> None: assert single_dispense_properties.position_reference.value == "well-bottom" assert single_dispense_properties.offset == Coordinate(x=0, y=0, z=-5) assert single_dispense_properties.flow_rate_by_volume.as_dict() == { - "default": 50.0, 10.0: 40.0, 20.0: 30.0, } @@ -101,7 +98,6 @@ def test_build_single_dispense_settings() -> None: assert single_dispense_properties.mix.repetitions == 3 assert single_dispense_properties.mix.volume == 15 assert single_dispense_properties.push_out_by_volume.as_dict() == { - "default": 5.0, 10.0: 7.0, 20.0: 10.0, } @@ -111,7 +107,7 @@ def test_build_single_dispense_settings() -> None: def test_build_multi_dispense_settings() -> None: """It should convert the shared data multi dispense settings to the PAPI type.""" - fixture_data = load_shared_data("liquid-class/fixtures/fixture_glycerol50.json") + fixture_data = load_shared_data("liquid-class/fixtures/1/fixture_glycerol50.json") liquid_class_model = LiquidClassSchemaV1.parse_raw(fixture_data) multi_dispense_data = liquid_class_model.byPipette[0].byTipType[0].multiDispense @@ -131,7 +127,6 @@ def test_build_multi_dispense_settings() -> None: assert multi_dispense_properties.retract.offset == Coordinate(x=0, y=0, z=5) assert multi_dispense_properties.retract.speed == 100 assert multi_dispense_properties.retract.air_gap_by_volume.as_dict() == { - "default": 2.0, 5.0: 3.0, 10.0: 4.0, } @@ -148,16 +143,13 @@ def test_build_multi_dispense_settings() -> None: assert multi_dispense_properties.position_reference.value == "well-bottom" assert multi_dispense_properties.offset == Coordinate(x=0, y=0, z=-5) assert multi_dispense_properties.flow_rate_by_volume.as_dict() == { - "default": 50.0, 10.0: 40.0, 20.0: 30.0, } assert multi_dispense_properties.conditioning_by_volume.as_dict() == { - "default": 10.0, 5.0: 5.0, } assert multi_dispense_properties.disposal_by_volume.as_dict() == { - "default": 2.0, 5.0: 3.0, } assert multi_dispense_properties.delay.enabled is True @@ -174,14 +166,12 @@ def test_build_multi_dispense_settings_none( def test_liquid_handling_property_by_volume() -> None: """It should create a class that can interpolate values and add and delete new points.""" - subject = LiquidHandlingPropertyByVolume({"default": 42, "5": 50, "10.0": 250}) - assert subject.as_dict() == {"default": 42, 5.0: 50, 10.0: 250} - assert subject.default == 42.0 + subject = LiquidHandlingPropertyByVolume([(5.0, 50.0), (10.0, 250.0)]) + assert subject.as_dict() == {5.0: 50, 10.0: 250} assert subject.get_for_volume(7) == 130.0 subject.set_for_volume(volume=7, value=175.5) assert subject.as_dict() == { - "default": 42, 5.0: 50, 10.0: 250, 7.0: 175.5, @@ -189,7 +179,7 @@ def test_liquid_handling_property_by_volume() -> None: assert subject.get_for_volume(7) == 175.5 subject.delete_for_volume(7) - assert subject.as_dict() == {"default": 42, 5.0: 50, 10.0: 250} + assert subject.as_dict() == {5.0: 50, 10.0: 250} assert subject.get_for_volume(7) == 130.0 with pytest.raises(KeyError, match="No value set for volume"): diff --git a/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py b/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py index 97de9fb0c48..20bbd2b646c 100644 --- a/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py +++ b/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py @@ -32,7 +32,7 @@ def test_liquid_class_creation_and_property_fetching( assert ( water.get_for( pipette_load_name, tiprack.load_name - ).dispense.flow_rate_by_volume.default + ).dispense.flow_rate_by_volume.get_for_volume(1) == 50 ) assert ( diff --git a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py index b11887a8824..038ea12255b 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py @@ -307,6 +307,7 @@ async def test_tip_attached_error( id="error-id", createdAt=datetime(year=1, month=2, day=3), wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (111, 222, 333)}, ), state_update=update_types.StateUpdate( pipette_location=update_types.PipetteLocationUpdate( diff --git a/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py index 9ea78e7dadd..5565ffea88c 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py @@ -14,13 +14,14 @@ DropTipInPlaceImplementation, ) from opentrons.protocol_engine.errors.exceptions import TipAttachedError -from opentrons.protocol_engine.execution import TipHandler +from opentrons.protocol_engine.execution import TipHandler, GantryMover from opentrons.protocol_engine.resources.model_utils import ModelUtils from opentrons.protocol_engine.state.update_types import ( PipetteTipStateUpdate, StateUpdate, PipetteUnknownFluidUpdate, ) +from opentrons.types import Point @pytest.fixture @@ -35,14 +36,23 @@ def mock_model_utils(decoy: Decoy) -> ModelUtils: return decoy.mock(cls=ModelUtils) +@pytest.fixture +def mock_gantry_mover(decoy: Decoy) -> GantryMover: + """Get a mock GantryMover.""" + return decoy.mock(cls=GantryMover) + + async def test_success( decoy: Decoy, mock_tip_handler: TipHandler, mock_model_utils: ModelUtils, + mock_gantry_mover: GantryMover, ) -> None: """A DropTip command should have an execution implementation.""" subject = DropTipInPlaceImplementation( - tip_handler=mock_tip_handler, model_utils=mock_model_utils + tip_handler=mock_tip_handler, + model_utils=mock_model_utils, + gantry_mover=mock_gantry_mover, ) params = DropTipInPlaceParams(pipetteId="abc", homeAfter=False) @@ -68,14 +78,20 @@ async def test_tip_attached_error( decoy: Decoy, mock_tip_handler: TipHandler, mock_model_utils: ModelUtils, + mock_gantry_mover: GantryMover, ) -> None: """A DropTip command should have an execution implementation.""" subject = DropTipInPlaceImplementation( - tip_handler=mock_tip_handler, model_utils=mock_model_utils + tip_handler=mock_tip_handler, + model_utils=mock_model_utils, + gantry_mover=mock_gantry_mover, ) params = DropTipInPlaceParams(pipetteId="abc", homeAfter=False) + decoy.when(await mock_gantry_mover.get_position(pipette_id="abc")).then_return( + Point(9, 8, 7) + ) decoy.when( await mock_tip_handler.drop_tip(pipette_id="abc", home_after=False) ).then_raise(TipAttachedError("Egads!")) @@ -92,6 +108,7 @@ async def test_tip_attached_error( id="error-id", createdAt=datetime(year=1, month=2, day=3), wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (9, 8, 7)}, ), state_update=StateUpdate( pipette_aspirated_fluid=PipetteUnknownFluidUpdate(pipette_id="abc") diff --git a/api/tests/opentrons/protocol_engine/commands/test_verify_tip_presence.py b/api/tests/opentrons/protocol_engine/commands/test_verify_tip_presence.py index 53eb1f5a59e..ef6d79629be 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_verify_tip_presence.py +++ b/api/tests/opentrons/protocol_engine/commands/test_verify_tip_presence.py @@ -23,13 +23,13 @@ async def test_verify_tip_presence_implementation( expectedState=TipPresenceStatus.PRESENT, ) - decoy.when( + result = await subject.execute(data) + + assert result == SuccessData(public=VerifyTipPresenceResult()) + decoy.verify( await tip_handler.verify_tip_presence( pipette_id="pipette-id", expected=TipPresenceStatus.PRESENT, + follow_singular_sensor=None, ) - ).then_return(None) - - result = await subject.execute(data) - - assert result == SuccessData(public=VerifyTipPresenceResult()) + ) diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_update_position_estimators.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_update_position_estimators.py index 79131994299..da381635ce3 100644 --- a/api/tests/opentrons/protocol_engine/commands/unsafe/test_update_position_estimators.py +++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_update_position_estimators.py @@ -37,11 +37,6 @@ async def test_update_position_estimators_implementation( decoy.when(gantry_mover.motor_axis_to_hardware_axis(MotorAxis.Y)).then_return( Axis.Y ) - decoy.when( - await ot3_hardware_api.update_axis_position_estimations( - [Axis.Z_L, Axis.P_L, Axis.X, Axis.Y] - ) - ).then_return(None) result = await subject.execute(data) diff --git a/api/tests/opentrons/protocol_engine/test_protocol_engine.py b/api/tests/opentrons/protocol_engine/test_protocol_engine.py index bc581114ab2..d7e4b32e02a 100644 --- a/api/tests/opentrons/protocol_engine/test_protocol_engine.py +++ b/api/tests/opentrons/protocol_engine/test_protocol_engine.py @@ -997,8 +997,7 @@ async def test_estop_noops_if_invalid( subject.estop() # Should not raise. decoy.verify( - action_dispatcher.dispatch(), # type: ignore - ignore_extra_args=True, + action_dispatcher.dispatch(expected_action), times=0, ) decoy.verify( diff --git a/api/tests/opentrons/protocol_runner/test_protocol_runner.py b/api/tests/opentrons/protocol_runner/test_protocol_runner.py index 2f06e27c2c2..15e0192175e 100644 --- a/api/tests/opentrons/protocol_runner/test_protocol_runner.py +++ b/api/tests/opentrons/protocol_runner/test_protocol_runner.py @@ -448,6 +448,7 @@ async def test_run_json_runner_stop_requested_stops_enqueuing( await run_func() +@pytest.mark.filterwarnings("ignore::decoy.warnings.RedundantVerifyWarning") @pytest.mark.parametrize( "schema_version, json_protocol", [ diff --git a/api/tests/opentrons/protocol_runner/test_run_orchestrator.py b/api/tests/opentrons/protocol_runner/test_run_orchestrator.py index ff0938a2e6d..c2cea3e0e7e 100644 --- a/api/tests/opentrons/protocol_runner/test_run_orchestrator.py +++ b/api/tests/opentrons/protocol_runner/test_run_orchestrator.py @@ -525,7 +525,7 @@ def get_next_to_execute() -> Generator[str, None, None]: index = index + 1 -async def test_create_error_recovery_policy( +def test_create_error_recovery_policy( decoy: Decoy, mock_protocol_engine: ProtocolEngine, live_protocol_subject: RunOrchestrator, diff --git a/app/src/assets/localization/en/device_settings.json b/app/src/assets/localization/en/device_settings.json index 5e40c7ce5e2..79416a09f73 100644 --- a/app/src/assets/localization/en/device_settings.json +++ b/app/src/assets/localization/en/device_settings.json @@ -177,6 +177,7 @@ "never": "Never", "new_features": "New Features", "next_step": "Next step", + "no_calibration_required": "No calibration required", "no_connection_found": "No connection found", "no_gripper_attached": "No gripper attached", "no_modules_attached": "No modules attached", diff --git a/app/src/assets/localization/en/protocol_command_text.json b/app/src/assets/localization/en/protocol_command_text.json index 4ff0039bbbd..2842f9dc30d 100644 --- a/app/src/assets/localization/en/protocol_command_text.json +++ b/app/src/assets/localization/en/protocol_command_text.json @@ -79,6 +79,7 @@ "tc_starting_extended_profile_cycle": "{{repetitions}} repetitions of the following steps:", "tc_starting_profile": "Running thermocycler profile with {{stepCount}} steps:", "touch_tip": "Touching tip", + "trash_bin": "Trash Bin", "trash_bin_in_slot": "Trash Bin in {{slot_name}}", "turning_rail_lights_off": "Turning rail lights off", "turning_rail_lights_on": "Turning rail lights on", diff --git a/app/src/local-resources/commands/utils/index.ts b/app/src/local-resources/commands/utils/index.ts index 7aa84d14de5..cc4e9c2579a 100644 --- a/app/src/local-resources/commands/utils/index.ts +++ b/app/src/local-resources/commands/utils/index.ts @@ -1 +1,2 @@ export * from './getCommandTextData' +export * from './lastRunCommandPromptedErrorRecovery' diff --git a/app/src/local-resources/commands/utils/lastRunCommandPromptedErrorRecovery.ts b/app/src/local-resources/commands/utils/lastRunCommandPromptedErrorRecovery.ts new file mode 100644 index 00000000000..dd07756ef43 --- /dev/null +++ b/app/src/local-resources/commands/utils/lastRunCommandPromptedErrorRecovery.ts @@ -0,0 +1,13 @@ +import type { RunCommandSummary } from '@opentrons/api-client' + +// Whether the last run protocol command prompted Error Recovery. +export function lastRunCommandPromptedErrorRecovery( + summary: RunCommandSummary[] +): boolean { + const lastProtocolCommand = summary.findLast( + command => command.intent !== 'fixit' && command.error != null + ) + + // All recoverable protocol commands have defined errors. + return lastProtocolCommand?.error?.isDefined ?? false +} diff --git a/app/src/local-resources/labware/utils/__tests__/getLabwareDisplayLocation.test.tsx b/app/src/local-resources/labware/utils/__tests__/getLabwareDisplayLocation.test.tsx index ca4b095f00e..beed2d012c0 100644 --- a/app/src/local-resources/labware/utils/__tests__/getLabwareDisplayLocation.test.tsx +++ b/app/src/local-resources/labware/utils/__tests__/getLabwareDisplayLocation.test.tsx @@ -137,6 +137,24 @@ describe('getLabwareDisplayLocation with translations', () => { screen.getByText('Slot C1') }) + it('should special case the slotName if it contains "waste chute"', () => { + render({ + location: { slotName: 'gripperWasteChute' }, + params: { ...defaultParams, detailLevel: 'slot-only' }, + }) + + screen.getByText('Waste Chute') + }) + + it('should special case the slotName if it contains "trash bin"', () => { + render({ + location: { slotName: 'trashBin' }, + params: { ...defaultParams, detailLevel: 'slot-only' }, + }) + + screen.getByText('Trash Bin') + }) + it('should handle an adapter on module location when the detail level is full', () => { const mockLoadedLabwares = [ { diff --git a/app/src/local-resources/labware/utils/getLabwareDisplayLocation.ts b/app/src/local-resources/labware/utils/getLabwareDisplayLocation.ts index 2e02199e667..63804cba764 100644 --- a/app/src/local-resources/labware/utils/getLabwareDisplayLocation.ts +++ b/app/src/local-resources/labware/utils/getLabwareDisplayLocation.ts @@ -4,6 +4,8 @@ import { getOccludedSlotCountForModule, THERMOCYCLER_MODULE_V1, THERMOCYCLER_MODULE_V2, + TRASH_BIN_FIXTURE, + WASTE_CHUTE_ADDRESSABLE_AREAS, } from '@opentrons/shared-data' import { getLabwareLocation } from './getLabwareLocation' @@ -12,6 +14,7 @@ import type { LocationSlotOnlyParams, LocationFullParams, } from './getLabwareLocation' +import type { AddressableAreaName } from '@opentrons/shared-data' export interface DisplayLocationSlotOnlyParams extends LocationSlotOnlyParams { t: TFunction @@ -47,7 +50,8 @@ export function getLabwareDisplayLocation( } // Simple slot location else if (moduleModel == null && adapterName == null) { - return isOnDevice ? slotName : t('slot', { slot_name: slotName }) + const validatedSlotCopy = handleSpecialSlotNames(slotName, t) + return isOnDevice ? validatedSlotCopy.odd : validatedSlotCopy.desktop } // Module location without adapter else if (moduleModel != null && adapterName == null) { @@ -91,3 +95,20 @@ export function getLabwareDisplayLocation( return '' } } + +// Sometimes we don't want to show the actual slotName, so we special case the text here. +function handleSpecialSlotNames( + slotName: string, + t: TFunction +): { odd: string; desktop: string } { + if (WASTE_CHUTE_ADDRESSABLE_AREAS.includes(slotName as AddressableAreaName)) { + return { odd: t('waste_chute'), desktop: t('waste_chute') } + } else if (slotName === TRASH_BIN_FIXTURE) { + return { odd: t('trash_bin'), desktop: t('trash_bin') } + } else { + return { + odd: slotName, + desktop: t('slot', { slot_name: slotName }), + } + } +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts index e41b7edc8ec..48887d4ac17 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts @@ -9,8 +9,13 @@ import { useTipAttachmentStatus, } from '/app/organisms/DropTipWizardFlows' import { useProtocolDropTipModal } from '../modals' -import { useCloseCurrentRun, useIsRunCurrent } from '/app/resources/runs' +import { + useCloseCurrentRun, + useCurrentRunCommands, + useIsRunCurrent, +} from '/app/resources/runs' import { isTerminalRunStatus } from '../../utils' +import { lastRunCommandPromptedErrorRecovery } from '/app/local-resources/commands' import type { RobotType } from '@opentrons/shared-data' import type { Run, RunStatus } from '@opentrons/api-client' @@ -102,6 +107,15 @@ export function useRunHeaderDropTip({ : { showDTWiz: false, dtWizProps: null } } + const runSummaryNoFixit = useCurrentRunCommands( + { + includeFixitCommands: false, + pageLength: 1, + cursor: null, + }, + { enabled: isTerminalRunStatus(runStatus) } + ) + // Manage tip checking useEffect(() => { // If a user begins a new run without navigating away from the run page, reset tip status. @@ -111,11 +125,14 @@ export function useRunHeaderDropTip({ } // Only determine tip status when necessary as this can be an expensive operation. Error Recovery handles tips, so don't // have to do it here if done during Error Recovery. - else if (isTerminalRunStatus(runStatus) && !enteredER) { + else if ( + runSummaryNoFixit != null && + !lastRunCommandPromptedErrorRecovery(runSummaryNoFixit) + ) { void determineTipStatus() } } - }, [runStatus, robotType, enteredER]) + }, [runStatus, robotType, runSummaryNoFixit]) // TODO(jh, 08-15-24): The enteredER condition is a hack, because errorCommands are only returned when a run is current. // Ideally the run should not need to be current to view errorCommands. diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx index 9aa6b7cee22..6acaf42445b 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx @@ -3,6 +3,8 @@ import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' import { when } from 'vitest-when' +import { useHoverTooltip } from '@opentrons/components' + import { renderWithProviders } from '/app/__testing-utils__' import { i18n } from '/app/i18n' import { useLPCSuccessToast } from '../../../hooks/useLPCSuccessToast' @@ -20,6 +22,13 @@ import { useUnmatchedModulesForProtocol, } from '/app/resources/runs' +vi.mock('@opentrons/components', async () => { + const actual = await vi.importActual('@opentrons/components') + return { + ...actual, + useHoverTooltip: vi.fn(), + } +}) vi.mock('../SetupLabwareList') vi.mock('../SetupLabwareMap') vi.mock('/app/organisms/LabwarePositionCheck') @@ -78,7 +87,6 @@ describe('SetupLabware', () => { .thenReturn({ complete: true, }) - when(vi.mocked(useRunHasStarted)).calledWith(RUN_ID).thenReturn(false) vi.mocked(getIsLabwareOffsetCodeSnippetsOn).mockReturnValue(false) vi.mocked(SetupLabwareMap).mockReturnValue(
mock setup labware map
@@ -88,6 +96,8 @@ describe('SetupLabware', () => { ) vi.mocked(useLPCDisabledReason).mockReturnValue(null) vi.mocked(useNotifyRunQuery).mockReturnValue({} as any) + vi.mocked(useHoverTooltip).mockReturnValue([{}, {}] as any) + vi.mocked(useRunHasStarted).mockReturnValue(false) }) afterEach(() => { @@ -98,8 +108,21 @@ describe('SetupLabware', () => { render() screen.getByText('mock setup labware list') screen.getByRole('button', { name: 'List View' }) + screen.getByRole('button', { name: 'Confirm placements' }) const mapView = screen.getByRole('button', { name: 'Map View' }) fireEvent.click(mapView) screen.getByText('mock setup labware map') }) + + it('disables the confirmation button if the run has already started', () => { + vi.mocked(useRunHasStarted).mockReturnValue(true) + + render() + + const btn = screen.getByRole('button', { + name: 'Confirm placements', + }) + + expect(btn).toBeDisabled() + }) }) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/index.tsx index 38963d79dda..687c1a739ab 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/index.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/index.tsx @@ -1,17 +1,22 @@ import { useTranslation } from 'react-i18next' import map from 'lodash/map' + import { JUSTIFY_CENTER, Flex, SPACING, PrimaryButton, DIRECTION_COLUMN, + Tooltip, + useHoverTooltip, } from '@opentrons/components' + import { useToggleGroup } from '/app/molecules/ToggleGroup/useToggleGroup' import { getModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' import { useMostRecentCompletedAnalysis, useModuleRenderInfoForProtocolById, + useRunHasStarted, } from '/app/resources/runs' import { useIsFlex } from '/app/redux-resources/robots' import { useStoredProtocolAnalysis } from '/app/resources/analysis' @@ -46,6 +51,11 @@ export function SetupLabware(props: SetupLabwareProps): JSX.Element { moduleModels ) + // TODO(jh, 11-13-24): These disabled tooltips are used throughout setup flows. Let's consolidate them. + const [targetProps, tooltipProps] = useHoverTooltip() + const runHasStarted = useRunHasStarted(runId) + const tooltipText = runHasStarted ? t('protocol_run_started') : null + return ( <> { setLabwareConfirmed(true) }} - disabled={labwareConfirmed} + disabled={labwareConfirmed || runHasStarted} + {...targetProps} > {t('confirm_placements')} + {tooltipText != null ? ( + {tooltipText} + ) : null} ) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx index 9d2c6223373..637a4814936 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx @@ -143,7 +143,6 @@ export function SetupLabwarePositionCheck( ) : null} { launchLPC() setIsShowingLPCSuccessToast(false) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx index 097f30447ee..736d5f5bc85 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx @@ -1,15 +1,26 @@ import type * as React from 'react' -import { describe, it, beforeEach, vi } from 'vitest' +import { describe, it, beforeEach, vi, expect } from 'vitest' import { screen, fireEvent } from '@testing-library/react' +import { useHoverTooltip } from '@opentrons/components' + import { renderWithProviders } from '/app/__testing-utils__' import { i18n } from '/app/i18n' import { SetupLiquids } from '../index' import { SetupLiquidsList } from '../SetupLiquidsList' import { SetupLiquidsMap } from '../SetupLiquidsMap' +import { useRunHasStarted } from '/app/resources/runs' +vi.mock('@opentrons/components', async () => { + const actual = await vi.importActual('@opentrons/components') + return { + ...actual, + useHoverTooltip: vi.fn(), + } +}) vi.mock('../SetupLiquidsList') vi.mock('../SetupLiquidsMap') +vi.mock('/app/resources/runs') describe('SetupLiquids', () => { const render = ( @@ -44,6 +55,8 @@ describe('SetupLiquids', () => { vi.mocked(SetupLiquidsMap).mockReturnValue(
Mock setup liquids map
) + vi.mocked(useHoverTooltip).mockReturnValue([{}, {}] as any) + vi.mocked(useRunHasStarted).mockReturnValue(false) }) it('renders the list and map view buttons and proceed button', () => { @@ -64,4 +77,15 @@ describe('SetupLiquids', () => { fireEvent.click(mapViewButton) screen.getByText('Mock setup liquids list') }) + it('disables the confirmation button if the run has already started', () => { + vi.mocked(useRunHasStarted).mockReturnValue(true) + + render(props) + + const btn = screen.getByRole('button', { + name: 'Confirm locations and volumes', + }) + + expect(btn).toBeDisabled() + }) }) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/index.tsx index 28a6f84e2d4..685d14a2ae5 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/index.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/index.tsx @@ -6,11 +6,14 @@ import { DIRECTION_COLUMN, ALIGN_CENTER, PrimaryButton, + useHoverTooltip, + Tooltip, } from '@opentrons/components' import { useToggleGroup } from '/app/molecules/ToggleGroup/useToggleGroup' import { ANALYTICS_LIQUID_SETUP_VIEW_TOGGLE } from '/app/redux/analytics' import { SetupLiquidsList } from './SetupLiquidsList' import { SetupLiquidsMap } from './SetupLiquidsMap' +import { useRunHasStarted } from '/app/resources/runs' import type { CompletedProtocolAnalysis, @@ -38,6 +41,12 @@ export function SetupLiquids({ t('map_view') as string, ANALYTICS_LIQUID_SETUP_VIEW_TOGGLE ) + + // TODO(jh, 11-13-24): These disabled tooltips are used throughout setup flows. Let's consolidate them. + const [targetProps, tooltipProps] = useHoverTooltip() + const runHasStarted = useRunHasStarted(runId) + const tooltipText = runHasStarted ? t('protocol_run_started') : null + return ( { setLiquidSetupConfirmed(true) }} - disabled={isLiquidSetupConfirmed} + disabled={isLiquidSetupConfirmed || runHasStarted} + {...targetProps} > {t('confirm_locations_and_volumes')}
+ {tooltipText != null ? ( + {tooltipText} + ) : null} ) diff --git a/app/src/organisms/Desktop/ProtocolDetails/index.tsx b/app/src/organisms/Desktop/ProtocolDetails/index.tsx index a54115a00f9..3bdf4be672e 100644 --- a/app/src/organisms/Desktop/ProtocolDetails/index.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/index.tsx @@ -47,7 +47,6 @@ import { parseInitialLoadedLabwareBySlot, parseInitialLoadedModulesBySlot, parseInitialPipetteNamesByMount, - NON_USER_ADDRESSABLE_LABWARE, } from '@opentrons/shared-data' import { getTopPortalEl } from '/app/App/portal' @@ -285,9 +284,7 @@ export function ProtocolDetails( : [] ), }).filter( - labware => - labware.result?.definition?.parameters?.format !== 'trash' && - !NON_USER_ADDRESSABLE_LABWARE.includes(labware?.params?.loadName) + labware => labware.result?.definition?.parameters?.format !== 'trash' ) : [] diff --git a/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx index 11ee8f60402..684b8269784 100644 --- a/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx @@ -8,7 +8,10 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { getModuleDisplayName } from '@opentrons/shared-data' +import { + getModuleDisplayName, + ABSORBANCE_READER_TYPE, +} from '@opentrons/shared-data' import { formatLastCalibrated } from './utils' import { ModuleCalibrationOverflowMenu } from './ModuleCalibrationOverflowMenu' @@ -41,42 +44,51 @@ export function ModuleCalibrationItems({ - {attachedModules.map(attachedModule => ( - - - - {getModuleDisplayName(attachedModule.moduleModel)} - - - - - {attachedModule.serialNumber} - - - - - {attachedModule.moduleOffset?.last_modified != null - ? formatLastCalibrated( - attachedModule.moduleOffset?.last_modified - ) - : t('not_calibrated_short')} - - - - - - - ))} + {attachedModules.map(attachedModule => { + const noCalibrationCopy = + attachedModule.moduleType === ABSORBANCE_READER_TYPE + ? t('no_calibration_required') + : t('not_calibrated_short') + + return ( + + + + {getModuleDisplayName(attachedModule.moduleModel)} + + + + + {attachedModule.serialNumber} + + + + + {attachedModule.moduleOffset?.last_modified != null + ? formatLastCalibrated( + attachedModule.moduleOffset?.last_modified + ) + : noCalibrationCopy} + + + + {attachedModule.moduleType !== ABSORBANCE_READER_TYPE ? ( + + ) : null} + + + ) + })} ) diff --git a/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationItems.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationItems.test.tsx index 2fdd9694e5d..8cb0dd62dc6 100644 --- a/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationItems.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationItems.test.tsx @@ -9,6 +9,7 @@ import { formatLastCalibrated } from '../utils' import { ModuleCalibrationItems } from '../ModuleCalibrationItems' import type { AttachedModule } from '@opentrons/api-client' +import { ABSORBANCE_READER_TYPE } from '@opentrons/shared-data' vi.mock('../ModuleCalibrationOverflowMenu') @@ -42,7 +43,7 @@ const mockCalibratedModule = { totalCycleCount: 1, currentStepIndex: 1, totalStepCount: 1, - }, + } as any, usbPort: { port: 3, portGroup: 'left', @@ -101,4 +102,23 @@ describe('ModuleCalibrationItems', () => { render(props) screen.getByText(formatLastCalibrated('2023-06-01T14:42:20.131798+00:00')) }) + + it('should say no calibration required if module is absorbance reader', () => { + const absorbanceReaderAttachedModule = { + ...mockCalibratedModule, + moduleType: ABSORBANCE_READER_TYPE, + moduleOffset: undefined, + } + props = { + ...props, + attachedModules: [ + absorbanceReaderAttachedModule as AttachedModule, + ] as AttachedModule[], + } + render(props) + expect( + screen.queryByText('mock ModuleCalibrationOverflowMenu') + ).not.toBeInTheDocument() + screen.getByText('No calibration required') + }) }) diff --git a/app/src/organisms/DropTipWizardFlows/DropTipWizard.tsx b/app/src/organisms/DropTipWizardFlows/DropTipWizard.tsx index be4cc9979ef..228d4f3384c 100644 --- a/app/src/organisms/DropTipWizardFlows/DropTipWizard.tsx +++ b/app/src/organisms/DropTipWizardFlows/DropTipWizard.tsx @@ -246,7 +246,13 @@ export const DropTipWizardContent = ( function buildModalContent(): JSX.Element { // Don't render the spinner screen for 1 render cycle on fixit commands. - if (currentStep === BEFORE_BEGINNING && issuedCommandsType === 'fixit') { + + if (errorDetails != null) { + return buildErrorScreen() + } else if ( + currentStep === BEFORE_BEGINNING && + issuedCommandsType === 'fixit' + ) { return buildBeforeBeginning() } else if ( activeMaintenanceRunId == null && @@ -259,8 +265,6 @@ export const DropTipWizardContent = ( return buildRobotInMotion() } else if (showConfirmExit) { return buildShowExitConfirmation() - } else if (errorDetails != null) { - return buildErrorScreen() } else if (currentStep === BEFORE_BEGINNING) { return buildBeforeBeginning() } else if (currentStep === CHOOSE_LOCATION_OPTION) { diff --git a/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipRouting.test.tsx b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipRouting.test.tsx index c6cf823784c..f5622960244 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipRouting.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipRouting.test.tsx @@ -144,4 +144,15 @@ describe('getInitialRouteAndStep', () => { expect(initialRoute).toBe(DT_ROUTES.DROP_TIP) expect(initialStep).toBe(DT_ROUTES.DROP_TIP[2]) }) + + it('should return the overridden route and first step when fixitUtils.routeOverride.route is provided but routeOverride.step is not provided', () => { + const fixitUtils = { + routeOverride: { route: DT_ROUTES.DROP_TIP, step: null }, + } as any + + const [initialRoute, initialStep] = getInitialRouteAndStep(fixitUtils) + + expect(initialRoute).toBe(DT_ROUTES.DROP_TIP) + expect(initialStep).toBe(DT_ROUTES.DROP_TIP[0]) + }) }) diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts index 6bb8915505e..493b4a77bff 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts @@ -369,12 +369,6 @@ const buildBlowoutCommands = ( ), }, }, - { - commandType: 'prepareToAspirate', - params: { - pipetteId: pipetteId ?? MANAGED_PIPETTE_ID, - }, - }, Z_HOME, ] : [ diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipRouting.tsx b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipRouting.tsx index b0928eca3c2..4c3e8e01064 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipRouting.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipRouting.tsx @@ -218,7 +218,10 @@ export function getInitialRouteAndStep( ): [DropTipFlowsRoute, DropTipFlowsStep] { const routeOverride = fixitUtils?.routeOverride const initialRoute = routeOverride?.route ?? DT_ROUTES.BEFORE_BEGINNING - const initialStep = routeOverride?.step ?? BEFORE_BEGINNING_STEPS[0] + const initialStep = + routeOverride?.step ?? + routeOverride?.route?.[0] ?? + BEFORE_BEGINNING_STEPS[0] return [initialRoute, initialStep] } diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/__tests__/getPipettesWithTipAttached.test.ts b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/__tests__/getPipettesWithTipAttached.test.ts index eb969f46820..bdaeff2dee0 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/__tests__/getPipettesWithTipAttached.test.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/__tests__/getPipettesWithTipAttached.test.ts @@ -20,11 +20,21 @@ const DROP_TIP_IN_PLACE = 'dropTipInPlace' const LOAD_PIPETTE = 'loadPipette' const FIXIT_INTENT = 'fixit' +const LEFT_PIPETTE = { + mount: LEFT, + state: { tipDetected: true }, + instrumentType: 'pipette', + ok: true, +} +const RIGHT_PIPETTE = { + mount: RIGHT, + state: { tipDetected: true }, + instrumentType: 'pipette', + ok: true, +} + const mockAttachedInstruments = { - data: [ - { mount: LEFT, state: { tipDetected: true } }, - { mount: RIGHT, state: { tipDetected: true } }, - ], + data: [LEFT_PIPETTE, RIGHT_PIPETTE], meta: { cursor: 0, totalLength: 2 }, } @@ -185,4 +195,45 @@ describe('getPipettesWithTipAttached', () => { const result = await getPipettesWithTipAttached(DEFAULT_PARAMS) expect(result).toEqual([mockAttachedInstruments.data[0]]) }) + + it('returns all valid attached pipettes when an error occurs', async () => { + vi.mocked(getCommands).mockRejectedValueOnce( + new Error('Example network error') + ) + + const result = await getPipettesWithTipAttached(DEFAULT_PARAMS) + + expect(result).toEqual([LEFT_PIPETTE, RIGHT_PIPETTE]) + }) + + it('filters out not ok pipettes', async () => { + vi.mocked(getCommands).mockRejectedValueOnce(new Error('Network error')) + + const mockInvalidPipettes = { + data: [ + LEFT_PIPETTE, + { + ...RIGHT_PIPETTE, + ok: false, + }, + ], + meta: { cursor: 0, totalLength: 2 }, + } + + const params = { + ...DEFAULT_PARAMS, + attachedInstruments: mockInvalidPipettes as any, + } + + const result = await getPipettesWithTipAttached(params) + + expect(result).toEqual([ + { + mount: LEFT, + state: { tipDetected: true }, + instrumentType: 'pipette', + ok: true, + }, + ]) + }) }) diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts index 42b006ca0b2..b710ba5a810 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts @@ -31,15 +31,17 @@ export function getPipettesWithTipAttached({ return Promise.resolve([]) } - return getCommandsExecutedDuringRun( - host as HostConfig, - runId - ).then(executedCmdData => - checkPipettesForAttachedTips( - executedCmdData.data, - runRecord.data.pipettes, - attachedInstruments.data as PipetteData[] - ) + return ( + getCommandsExecutedDuringRun(host as HostConfig, runId) + .then(executedCmdData => + checkPipettesForAttachedTips( + executedCmdData.data, + runRecord.data.pipettes, + attachedInstruments.data as PipetteData[] + ) + ) + // If any network error occurs, return all attached pipettes as having tips attached for safety reasons. + .catch(() => Promise.resolve(getPipettesDataFrom(attachedInstruments))) ) } @@ -61,7 +63,12 @@ function getCommandsExecutedDuringRun( }) } -const TIP_EXCHANGE_COMMAND_TYPES = ['dropTip', 'dropTipInPlace', 'pickUpTip'] +const TIP_EXCHANGE_COMMAND_TYPES = [ + 'dropTip', + 'dropTipInPlace', + 'pickUpTip', + 'moveToAddressableAreaForDropTip', +] function checkPipettesForAttachedTips( commands: RunCommandSummary[], @@ -119,8 +126,10 @@ function checkPipettesForAttachedTips( } // Convert the array of mounts with attached tips to PipetteData with attached tips. - const pipettesWithTipAttached = attachedPipettes.filter(attachedPipette => - mountsWithTipAttached.includes(attachedPipette.mount) + const pipettesWithTipAttached = attachedPipettes.filter( + attachedPipette => + mountsWithTipAttached.includes(attachedPipette.mount) && + attachedPipette.ok ) // Preferentially assign the left mount as the first element. @@ -136,3 +145,13 @@ function checkPipettesForAttachedTips( return pipettesWithTipAttached } + +function getPipettesDataFrom( + attachedInstruments: Instruments | null +): PipetteData[] { + return attachedInstruments != null + ? (attachedInstruments.data.filter( + instrument => instrument.instrumentType === 'pipette' && instrument.ok + ) as PipetteData[]) + : [] +} diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryError.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryError.tsx index dd680ed24f6..d2fe92438a4 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryError.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryError.tsx @@ -92,6 +92,7 @@ export function RecoveryDropTipFlowErrors({ routeUpdateActions, getRecoveryOptionCopy, errorKind, + subMapUtils, }: RecoveryContentProps): JSX.Element { const { t } = useTranslation('error_recovery') const { step } = recoveryMap @@ -108,6 +109,9 @@ export function RecoveryDropTipFlowErrors({ errorKind ) + // Whenever there is an error during drop tip wizard, reset the submap so properly re-entry routing occurs. + subMapUtils.updateSubMap(null) + const buildTitle = (): string => { switch (step) { case ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_GENERAL_ERROR: diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx index 0cd49fec3ea..57eef74d2d6 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx @@ -202,6 +202,7 @@ export function useDropTipFlowUtils({ subMapUtils, routeUpdateActions, recoveryMap, + errorKind, }: RecoveryContentProps): FixitCommandTypeUtils { const { t } = useTranslation('error_recovery') const { @@ -210,7 +211,7 @@ export function useDropTipFlowUtils({ ERROR_WHILE_RECOVERING, DROP_TIP_FLOWS, } = RECOVERY_MAP - const { runId } = tipStatusUtils + const { runId, gripperErrorFirstPipetteWithTip } = tipStatusUtils const { step } = recoveryMap const { selectedRecoveryOption } = currentRecoveryOptionUtils const { proceedToRouteAndStep } = routeUpdateActions @@ -304,11 +305,12 @@ export function useDropTipFlowUtils({ } const pipetteId = - failedCommand != null && + gripperErrorFirstPipetteWithTip ?? + (failedCommand != null && 'params' in failedCommand.byRunRecord && 'pipetteId' in failedCommand.byRunRecord.params ? failedCommand.byRunRecord.params.pipetteId - : null + : null) return { runId, diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryError.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryError.test.tsx index 1394993746b..f46f3f949ba 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryError.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryError.test.tsx @@ -25,12 +25,14 @@ describe('RecoveryError', () => { let getRecoverOptionCopyMock: Mock let handleMotionRoutingMock: Mock let homePipetteZAxesMock: Mock + let updateSubMapMock: Mock beforeEach(() => { proceedToRouteAndStepMock = vi.fn() getRecoverOptionCopyMock = vi.fn() handleMotionRoutingMock = vi.fn().mockResolvedValue(undefined) homePipetteZAxesMock = vi.fn().mockResolvedValue(undefined) + updateSubMapMock = vi.fn() props = { ...mockRecoveryContentProps, @@ -48,6 +50,7 @@ describe('RecoveryError', () => { route: ERROR_WHILE_RECOVERING.ROUTE, step: ERROR_WHILE_RECOVERING.STEPS.RECOVERY_ACTION_FAILED, }, + subMapUtils: { subMap: null, updateSubMap: updateSubMapMock }, } getRecoverOptionCopyMock.mockReturnValue('Retry step') @@ -95,7 +98,7 @@ describe('RecoveryError', () => { expect(screen.queryAllByText('Continue to drop tip')[0]).toBeInTheDocument() }) - it(`renders RecoveryDropTipFlowErrors when step is ${ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_TIP_DROP_FAILED}`, () => { + it(`renders RecoveryDropTipFlowErrors when step is ${ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_TIP_DROP_FAILED} and resets the submap`, () => { props.recoveryMap.step = RECOVERY_MAP.ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_TIP_DROP_FAILED render(props) @@ -107,6 +110,7 @@ describe('RecoveryError', () => { )[0] ).toBeInTheDocument() expect(screen.queryAllByText('Return to menu')[0]).toBeInTheDocument() + expect(updateSubMapMock).toHaveBeenCalledWith(null) }) it(`calls proceedToRouteAndStep with ${RECOVERY_MAP.OPTION_SELECTION.ROUTE} when the "Return to menu" button is clicked in RecoveryDropTipFlowErrors with step ${ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_GENERAL_ERROR}`, () => { diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts index ca2e086d9fd..21307b8e4e8 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts @@ -139,24 +139,41 @@ describe('useRecoveryCommands', () => { false ) }) - ;([ + + const IN_PLACE_COMMANDS = [ 'aspirateInPlace', 'dispenseInPlace', 'blowOutInPlace', 'dropTipInPlace', 'prepareToAspirate', - ] as const).forEach(inPlaceCommandType => { - it(`Should move to retryLocation if failed command is ${inPlaceCommandType} and error is appropriate when retrying`, async () => { + ] as const + + const ERROR_SCENARIOS = [ + { type: 'overpressure', code: '3006' }, + { type: 'tipPhysicallyAttached', code: '3007' }, + ] as const + + it.each( + ERROR_SCENARIOS.flatMap(error => + IN_PLACE_COMMANDS.map(commandType => ({ + errorType: error.type, + errorCode: error.code, + commandType, + })) + ) + )( + 'Should move to retryLocation if failed command is $commandType and error is $errorType when retrying', + async ({ errorType, errorCode, commandType }) => { const { result } = renderHook(() => { const failedCommand = { ...mockFailedCommand, - commandType: inPlaceCommandType, + commandType, params: { pipetteId: 'mock-pipette-id', }, error: { - errorType: 'overpressure', - errorCode: '3006', + errorType, + errorCode, isDefined: true, errorInfo: { retryLocation: [1, 2, 3], @@ -180,9 +197,11 @@ describe('useRecoveryCommands', () => { selectedRecoveryOption: RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE, }) }) + await act(async () => { await result.current.retryFailedCommand() }) + expect(mockChainRunCommands).toHaveBeenLastCalledWith( [ { @@ -194,14 +213,14 @@ describe('useRecoveryCommands', () => { }, }, { - commandType: inPlaceCommandType, + commandType, params: { pipetteId: 'mock-pipette-id' }, }, ], false ) - }) - }) + } + ) it('should call resumeRun with runId and show success toast on success', async () => { const { result } = renderHook(() => useRecoveryCommands(props)) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts index 533b30aa6c4..ecf03e4f56b 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts @@ -139,6 +139,7 @@ export function useERUtils({ const tipStatusUtils = useRecoveryTipStatus({ runId, runRecord, + failedCommand, attachedInstruments, failedPipetteInfo, }) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts index 4ce5194aca4..65bd77eed0b 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts @@ -9,7 +9,7 @@ import { } from '@opentrons/react-api-client' import { useChainRunCommands } from '/app/resources/runs' -import { ERROR_KINDS, RECOVERY_MAP } from '../constants' +import { DEFINED_ERROR_TYPES, ERROR_KINDS, RECOVERY_MAP } from '../constants' import { getErrorKind } from '/app/organisms/ErrorRecoveryFlows/utils' import type { @@ -127,6 +127,7 @@ export function useRecoveryCommands({ | DispenseInPlaceRunTimeCommand | DropTipInPlaceRunTimeCommand | PrepareToAspirateRunTimeCommand + const IN_PLACE_COMMAND_TYPES = [ 'aspirateInPlace', 'dispenseInPlace', @@ -134,16 +135,25 @@ export function useRecoveryCommands({ 'dropTipInPlace', 'prepareToAspirate', ] as const + + const RETRY_ERROR_TYPES = [ + DEFINED_ERROR_TYPES.OVERPRESSURE, + DEFINED_ERROR_TYPES.TIP_PHYSICALLY_ATTACHED, + ] as const + const isInPlace = ( failedCommand: FailedCommand ): failedCommand is InPlaceCommand => IN_PLACE_COMMAND_TYPES.includes( (failedCommand as InPlaceCommand).commandType ) + return unvalidatedFailedCommand != null ? isInPlace(unvalidatedFailedCommand) ? unvalidatedFailedCommand.error?.isDefined && - unvalidatedFailedCommand.error?.errorType === 'overpressure' && + RETRY_ERROR_TYPES.includes( + unvalidatedFailedCommand.error?.errorType + ) && // Paranoia: this value comes from the wire and may be unevenly implemented typeof unvalidatedFailedCommand.error?.errorInfo?.retryLocation?.at( 0 diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts index a715d12c83f..0a12b59d089 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts @@ -1,18 +1,22 @@ import { useState } from 'react' import head from 'lodash/head' -import { useHost } from '@opentrons/react-api-client' +import { useHost, useRunCurrentState } from '@opentrons/react-api-client' import { getPipetteModelSpecs } from '@opentrons/shared-data' import { useTipAttachmentStatus } from '/app/organisms/DropTipWizardFlows' +import { ERROR_KINDS } from '/app/organisms/ErrorRecoveryFlows/constants' +import { getErrorKind } from '/app/organisms/ErrorRecoveryFlows/utils' import type { Run, Instruments, PipetteData } from '@opentrons/api-client' import type { PipetteWithTip, TipAttachmentStatusResult, } from '/app/organisms/DropTipWizardFlows' +import type { ERUtilsProps } from '/app/organisms/ErrorRecoveryFlows/hooks/useERUtils' interface UseRecoveryTipStatusProps { runId: string + failedCommand: ERUtilsProps['failedCommand'] failedPipetteInfo: PipetteData | null attachedInstruments?: Instruments runRecord?: Run @@ -22,6 +26,7 @@ export type RecoveryTipStatusUtils = TipAttachmentStatusResult & { /* Whether the robot is currently determineTipStatus() */ isLoadingTipStatus: boolean runId: string + gripperErrorFirstPipetteWithTip: string | null } // Wraps the tip attachment status utils with Error Recovery specific states and values. @@ -77,11 +82,26 @@ export function useRecoveryTipStatus( }) } + // TODO(jh, 11-15-24): This is temporary. Collaborate with design a better way to do drop tip wizard for multiple + // pipettes during error recovery. The tip detection logic will shortly be simplified, too! + const errorKind = getErrorKind(props.failedCommand) + const currentTipStates = + useRunCurrentState(props.runId, { + enabled: errorKind === ERROR_KINDS.GRIPPER_ERROR, + }).data?.data.tipStates ?? null + + const gripperErrorFirstPipetteWithTip = + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + Object.entries(currentTipStates ?? {}).find( + ([_, state]) => state.hasTip + )?.[0] ?? null + return { ...tipAttachmentStatusUtils, aPipetteWithTip: failedCommandPipette, determineTipStatus: determineTipStatusWithLoading, isLoadingTipStatus, runId: props.runId, + gripperErrorFirstPipetteWithTip, } } diff --git a/app/src/organisms/GripperWizardFlows/index.tsx b/app/src/organisms/GripperWizardFlows/index.tsx index 34f07d39a35..1a4bab512eb 100644 --- a/app/src/organisms/GripperWizardFlows/index.tsx +++ b/app/src/organisms/GripperWizardFlows/index.tsx @@ -71,6 +71,7 @@ export function GripperWizardFlows( const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = useState< string | null >(null) + const [errorMessage, setErrorMessage] = useState(null) // we should start checking for run deletion only after the maintenance run is created // and the useCurrentRun poll has returned that created id @@ -86,6 +87,9 @@ export function GripperWizardFlows( onSuccess: response => { setCreatedMaintenanceRunId(response.data.id) }, + onError: error => { + setErrorMessage(error.message) + }, }) const { data: maintenanceRunData } = useNotifyCurrentMaintenanceRun({ @@ -117,7 +121,6 @@ export function GripperWizardFlows( ]) const [isExiting, setIsExiting] = useState(false) - const [errorMessage, setErrorMessage] = useState(null) const handleClose = (): void => { if (props?.onComplete != null) { @@ -298,9 +301,12 @@ export const GripperWizard = ( isRobotMoving={isRobotMoving} /> ) - } else if ( + } + // These flows often have custom error messaging, so this fallback modal is shown only in specific circumstances. + else if ( (isExiting && errorMessage != null) || - maintenanceRunStatus === RUN_STATUS_FAILED + maintenanceRunStatus === RUN_STATUS_FAILED || + (errorMessage != null && createdMaintenanceRunId == null) ) { onExit = handleClose modalContent = ( diff --git a/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts b/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts index 8c2831115c9..4e8119e4d74 100644 --- a/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts +++ b/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts @@ -3,7 +3,6 @@ import { HEATERSHAKER_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, ABSORBANCE_READER_TYPE, - NON_USER_ADDRESSABLE_LABWARE, } from '@opentrons/shared-data' import type { @@ -49,8 +48,7 @@ export function getPrepCommands( return [...acc, loadWithPipetteId] } else if ( command.commandType === 'loadLabware' && - command.result?.labwareId != null && - !NON_USER_ADDRESSABLE_LABWARE.includes(command.params.loadName) + command.result?.labwareId != null ) { // load all labware off-deck so that LPC can move them on individually later return [ diff --git a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx index a22c7591360..a668fc48e70 100644 --- a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx +++ b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx @@ -150,7 +150,7 @@ export const ModuleOverflowMenu = ( item.onClick(item.isSecondary)} - disabled={item.disabledReason || isDisabled} + disabled={item.isSettingDisabled} whiteSpace={NO_WRAP} > {item.setSetting} diff --git a/app/src/organisms/ModuleCard/hooks.tsx b/app/src/organisms/ModuleCard/hooks.tsx index 10b0e9fd30b..da44c64d983 100644 --- a/app/src/organisms/ModuleCard/hooks.tsx +++ b/app/src/organisms/ModuleCard/hooks.tsx @@ -80,6 +80,7 @@ export function useLatchControls(module: AttachedModule): LatchControls { export type MenuItemsByModuleType = { [moduleType in AttachedModule['moduleType']]: Array<{ setSetting: string + isSettingDisabled: boolean isSecondary: boolean menuButtons: JSX.Element[] | null onClick: (isSecondary: boolean) => void @@ -267,6 +268,7 @@ export function useModuleOverflowMenu( module.data.lidTargetTemperature != null ? t('overflow_menu_deactivate_lid') : t('overflow_menu_lid_temp'), + isSettingDisabled: isDisabled, isSecondary: true, menuButtons: null, onClick: @@ -285,6 +287,7 @@ export function useModuleOverflowMenu( module.data.lidStatus === 'open' ? t('close_lid') : t('open_lid'), + isSettingDisabled: isDisabled, isSecondary: false, menuButtons: [thermoSetBlockTempBtn, aboutModuleBtn], onClick: controlTCLid, @@ -298,6 +301,7 @@ export function useModuleOverflowMenu( ? t('overflow_menu_deactivate_temp') : t('overflow_menu_mod_temp'), isSecondary: false, + isSettingDisabled: isDisabled, menuButtons: [aboutModuleBtn], onClick: module.data.status !== 'idle' @@ -317,6 +321,7 @@ export function useModuleOverflowMenu( ? t('overflow_menu_disengage') : t('overflow_menu_engage'), isSecondary: false, + isSettingDisabled: isDisabled, menuButtons: [aboutModuleBtn], onClick: module.data.status !== 'disengaged' @@ -336,6 +341,7 @@ export function useModuleOverflowMenu( ? t('heater_shaker:deactivate_heater') : t('heater_shaker:set_temperature'), isSecondary: false, + isSettingDisabled: isDisabled, menuButtons: [ labwareLatchBtn, aboutModuleBtn, @@ -358,6 +364,7 @@ export function useModuleOverflowMenu( { setSetting: t('overflow_menu_about'), isSecondary: false, + isSettingDisabled: false, menuButtons: [], onClick: handleAboutClick, }, diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx index d8dcf237caa..bcaaca86182 100644 --- a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx @@ -11,8 +11,8 @@ import { DIRECTION_ROW, Flex, LegacyStyledText, - SPACING, RadioButton, + SPACING, truncateString, TYPOGRAPHY, } from '@opentrons/components' diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx index a80ea2b1f84..ec4df679049 100644 --- a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx @@ -321,7 +321,6 @@ export function ProtocolSetupParameters({ ) - // ToDo (kk:06/18/2024) ff will be removed when we freeze the code if (chooseCsvFileScreen != null) { children = ( - {detail} + {title === 'CSV File' && detail != null + ? truncateString(detail, CSV_FILE_MAX_LENGTH) + : detail} {subDetail != null && detail != null ?
: null} {subDetail} diff --git a/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx b/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx index 17ef7de2c3c..e522fb1dae7 100644 --- a/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx +++ b/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx @@ -160,7 +160,8 @@ export function ProtocolWithLastRun({ } // TODO(BC, 2023-06-05): see if addSuffix false allow can remove usage of .replace here const formattedLastRunTime = formatDistance( - new Date(runData.createdAt), + // Fallback to current date if completedAt is null, though this should never happen since runs must be completed to appear in dashboard + new Date(runData.completedAt ?? new Date()), new Date(), { addSuffix: true, diff --git a/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx b/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx index e03f548f069..10ee119176e 100644 --- a/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx +++ b/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx @@ -87,7 +87,7 @@ const missingBoth = [ const mockRunData = { id: RUN_ID, createdAt: '2022-05-03T21:36:12.494778+00:00', - completedAt: 'thistime', + completedAt: '2023-05-03T21:36:12.494778+00:00', startedAt: 'thistime', protocolId: 'mockProtocolId', status: RUN_STATUS_FAILED, @@ -169,7 +169,7 @@ describe('RecentRunProtocolCard', () => { it('should render text', () => { render(props) const lastRunTime = formatDistance( - new Date(mockRunData.createdAt), + new Date(mockRunData.completedAt), new Date(), { addSuffix: true, diff --git a/app/src/organisms/PipetteWizardFlows/index.tsx b/app/src/organisms/PipetteWizardFlows/index.tsx index 39022810b56..ed11df4352d 100644 --- a/app/src/organisms/PipetteWizardFlows/index.tsx +++ b/app/src/organisms/PipetteWizardFlows/index.tsx @@ -113,6 +113,7 @@ export const PipetteWizardFlows = ( const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = useState< string | null >(null) + const [errorMessage, setShowErrorMessage] = useState(null) // we should start checking for run deletion only after the maintenance run is created // and the useCurrentRun poll has returned that created id const [ @@ -143,6 +144,9 @@ export const PipetteWizardFlows = ( onSuccess: response => { setCreatedMaintenanceRunId(response.data.id) }, + onError: error => { + setShowErrorMessage(error.message) + }, }, host ) @@ -169,7 +173,6 @@ export const PipetteWizardFlows = ( closeFlow, ]) - const [errorMessage, setShowErrorMessage] = useState(null) const [isExiting, setIsExiting] = useState(false) const proceed = (): void => { if (!isCommandMutationLoading) { @@ -281,9 +284,11 @@ export const PipetteWizardFlows = ( let onExit if (currentStep == null) return null let modalContent: JSX.Element =
UNASSIGNED STEP
+ // These flows often have custom error messaging, so this fallback modal is shown only in specific circumstances. if ( (isExiting && errorMessage != null) || - maintenanceRunData?.data.status === RUN_STATUS_FAILED + maintenanceRunData?.data.status === RUN_STATUS_FAILED || + (errorMessage != null && createdMaintenanceRunId == null) ) { modalContent = ( (false) + const [showIcon, setShowIcon] = useState(false) const [ showFailedAnalysisModal, setShowFailedAnalysisModal, - ] = React.useState(false) + ] = useState(false) const { t, i18n } = useTranslation(['protocol_info', 'branded']) const protocolName = protocol.metadata.protocolName ?? protocol.files[0].name const longpress = useLongPress() const queryClient = useQueryClient() const host = useHost() + const updatedLastRun = useUpdatedLastRunTime(lastRun) const { id: protocolId, analysisSummaries } = protocol const { @@ -121,7 +122,7 @@ export function ProtocolCard(props: ProtocolCardProps): JSX.Element { } } - React.useEffect(() => { + useEffect(() => { if (longpress.isLongPressed) { longPress(true) setTargetProtocolId(protocol.id) @@ -195,13 +196,8 @@ export function ProtocolCard(props: ProtocolCardProps): JSX.Element { if (isFailedAnalysis) protocolCardBackgroundColor = COLORS.red35 if (isRequiredCSV) protocolCardBackgroundColor = COLORS.yellow35 - const textWrap = (lastRun?: string): string => { - if (lastRun != null) { - lastRun = formatDistance(new Date(lastRun), new Date(), { - addSuffix: true, - }).replace('about ', '') - } - return lastRun === 'less than a minute ago' ? 'normal' : 'nowrap' + const textWrap = (updatedLastRun: string): string => { + return updatedLastRun === 'less than a minute ago' ? 'normal' : 'nowrap' } return ( @@ -257,13 +253,9 @@ export function ProtocolCard(props: ProtocolCardProps): JSX.Element { - {lastRun != null - ? formatDistance(new Date(lastRun), new Date(), { - addSuffix: true, - }).replace('about ', '') - : t('no_history')} + {updatedLastRun} diff --git a/app/src/pages/ODD/ProtocolDashboard/hooks.ts b/app/src/pages/ODD/ProtocolDashboard/hooks.ts new file mode 100644 index 00000000000..6db041786ff --- /dev/null +++ b/app/src/pages/ODD/ProtocolDashboard/hooks.ts @@ -0,0 +1,38 @@ +import { useEffect, useState } from 'react' +import { formatDistance } from 'date-fns' +import { useTranslation } from 'react-i18next' + +import type { TFunction } from 'i18next' + +const UPDATE_TIME_INTERVAL_MS = 60000 + +// Given the last run timestamp, update the time since the last run on an interval. +export function useUpdatedLastRunTime(lastRun: string | undefined): string { + const { t } = useTranslation(['protocol_info']) + + const [updatedLastRun, setUpdatedLastRun] = useState(() => + computeLastRunFromNow(lastRun, t as TFunction) + ) + useEffect(() => { + const timer = setInterval(() => { + setUpdatedLastRun(computeLastRunFromNow(lastRun, t as TFunction)) + }, UPDATE_TIME_INTERVAL_MS) + + return () => { + clearInterval(timer) + } + }, [lastRun, t]) + + return updatedLastRun +} + +function computeLastRunFromNow( + lastRun: string | undefined, + t: TFunction +): string { + return lastRun != null + ? formatDistance(new Date(lastRun), new Date(), { + addSuffix: true, + }).replace('about ', '') + : t('no_history') +} diff --git a/app/src/pages/ODD/ProtocolDashboard/index.tsx b/app/src/pages/ODD/ProtocolDashboard/index.tsx index de775795ded..cddc9ee0a1f 100644 --- a/app/src/pages/ODD/ProtocolDashboard/index.tsx +++ b/app/src/pages/ODD/ProtocolDashboard/index.tsx @@ -252,9 +252,10 @@ export function ProtocolDashboard(): JSX.Element { {sortedProtocols.map(protocol => { - const lastRun = runs.data?.data.find( + // Run data is ordered based on timestamp. We want the last time a matching run was ran. + const lastRun = runs.data?.data.findLast( run => run.protocolId === protocol.id - )?.createdAt + )?.completedAt return ( { - if (isRunCurrent && enteredER === false) { + if ( + isRunCurrent && + runSummaryNoFixit != null && + !lastRunCommandPromptedErrorRecovery(runSummaryNoFixit) + ) { + console.log('HITTING THIS') void determineTipStatus() } - }, [isRunCurrent, enteredER]) + }, [runSummaryNoFixit, isRunCurrent]) const returnToQuickTransfer = (): void => { closeCurrentRunIfValid(() => { diff --git a/app/src/redux-resources/runs/hooks/useRequiredSetupStepsInOrder.ts b/app/src/redux-resources/runs/hooks/useRequiredSetupStepsInOrder.ts index 481a3622f05..31863f238e6 100644 --- a/app/src/redux-resources/runs/hooks/useRequiredSetupStepsInOrder.ts +++ b/app/src/redux-resources/runs/hooks/useRequiredSetupStepsInOrder.ts @@ -56,14 +56,19 @@ const keysInOrder = ( protocolAnalysis == null ? NO_ANALYSIS_STEPS_IN_ORDER : ALL_STEPS_IN_ORDER.filter((stepKey: StepKey) => { - if (protocolAnalysis.modules.length === 0) { - return stepKey !== MODULE_SETUP_STEP_KEY + if ( + stepKey === MODULE_SETUP_STEP_KEY && + protocolAnalysis.modules.length === 0 + ) { + return false + } else if ( + stepKey === LIQUID_SETUP_STEP_KEY && + protocolAnalysis.liquids.length === 0 + ) { + return false + } else { + return true } - - if (protocolAnalysis.liquids.length === 0) { - return stepKey !== LIQUID_SETUP_STEP_KEY - } - return true }) return { orderedSteps: orderedSteps as StepKey[], orderedApplicableSteps } } diff --git a/app/src/transformations/analysis/getProtocolModulesInfo.ts b/app/src/transformations/analysis/getProtocolModulesInfo.ts index ee1da1a2392..8a268c2694b 100644 --- a/app/src/transformations/analysis/getProtocolModulesInfo.ts +++ b/app/src/transformations/analysis/getProtocolModulesInfo.ts @@ -3,7 +3,6 @@ import { getModuleDef2, getLoadedLabwareDefinitionsByUri, getPositionFromSlotId, - NON_USER_ADDRESSABLE_LABWARE, } from '@opentrons/shared-data' import { getModuleInitialLoadInfo } from '../commands' import type { @@ -39,8 +38,7 @@ export const getProtocolModulesInfo = ( protocolData.commands .filter( (command): command is LoadLabwareRunTimeCommand => - command.commandType === 'loadLabware' && - !NON_USER_ADDRESSABLE_LABWARE.includes(command.params.loadName) + command.commandType === 'loadLabware' ) .find( (command: LoadLabwareRunTimeCommand) => diff --git a/app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts b/app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts index 30c79281649..ebc31e37ccc 100644 --- a/app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts +++ b/app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts @@ -1,8 +1,5 @@ import partition from 'lodash/partition' -import { - getLabwareDisplayName, - NON_USER_ADDRESSABLE_LABWARE, -} from '@opentrons/shared-data' +import { getLabwareDisplayName } from '@opentrons/shared-data' import type { LabwareDefinition2, @@ -46,8 +43,7 @@ export function getLabwareSetupItemGroups( commands.reduce((acc, c) => { if ( c.commandType === 'loadLabware' && - c.result?.definition?.metadata?.displayCategory !== 'trash' && - !NON_USER_ADDRESSABLE_LABWARE.includes(c.params?.loadName) + c.result?.definition?.metadata?.displayCategory !== 'trash' ) { const { location, displayName } = c.params const { definition } = c.result ?? {} diff --git a/components/src/atoms/Checkbox/index.tsx b/components/src/atoms/Checkbox/index.tsx index 8ace61cb0bf..44c2ba8ee04 100644 --- a/components/src/atoms/Checkbox/index.tsx +++ b/components/src/atoms/Checkbox/index.tsx @@ -48,7 +48,7 @@ export function Checkbox(props: CheckboxProps): JSX.Element { align-items: ${ALIGN_CENTER}; flex-direction: ${DIRECTION_ROW}; color: ${isChecked ? COLORS.white : COLORS.black90}; - background-color: ${isChecked ? COLORS.blue50 : COLORS.blue35}; + background-color: ${isChecked ? COLORS.blue50 : COLORS.blue30}; border-radius: ${type === 'round' ? BORDERS.borderRadiusFull : BORDERS.borderRadius8}; @@ -68,6 +68,9 @@ export function Checkbox(props: CheckboxProps): JSX.Element { background-color: ${COLORS.grey35}; color: ${COLORS.grey50}; } + &:hover { + background-color: ${isChecked ? COLORS.blue55 : COLORS.blue35}; + } @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { padding: ${SPACING.spacing20}; diff --git a/components/src/atoms/InputField/index.tsx b/components/src/atoms/InputField/index.tsx index 59b404b476f..06b4ad68533 100644 --- a/components/src/atoms/InputField/index.tsx +++ b/components/src/atoms/InputField/index.tsx @@ -6,6 +6,7 @@ import { ALIGN_CENTER, DIRECTION_COLUMN, DIRECTION_ROW, + NO_WRAP, TEXT_ALIGN_RIGHT, } from '../../styles' import { BORDERS, COLORS } from '../../helix-design-system' @@ -251,6 +252,7 @@ export const InputField = React.forwardRef( font-weight: ${TYPOGRAPHY.fontWeightRegular}; line-height: ${TYPOGRAPHY.lineHeight28}; justify-content: ${textAlign}; + white-space: ${NO_WRAP}; } ` diff --git a/components/src/atoms/Tag/index.tsx b/components/src/atoms/Tag/index.tsx index c41025dd25b..74c72da486e 100644 --- a/components/src/atoms/Tag/index.tsx +++ b/components/src/atoms/Tag/index.tsx @@ -1,7 +1,7 @@ import { css } from 'styled-components' import { BORDERS, COLORS } from '../../helix-design-system' import { Flex } from '../../primitives' -import { ALIGN_CENTER, DIRECTION_ROW } from '../../styles' +import { ALIGN_CENTER, DIRECTION_ROW, FLEX_MAX_CONTENT } from '../../styles' import { RESPONSIVENESS, SPACING, TYPOGRAPHY } from '../../ui-style-constants' import { Icon } from '../../icons' import { LegacyStyledText } from '../StyledText' @@ -19,6 +19,7 @@ export interface TagProps { iconPosition?: 'left' | 'right' /** Tagicon */ iconName?: IconName + shrinkToContent?: boolean } const defaultColors = { @@ -42,11 +43,12 @@ const TAG_PROPS_BY_TYPE: Record< } export function Tag(props: TagProps): JSX.Element { - const { iconName, type, text, iconPosition } = props + const { iconName, type, text, iconPosition, shrinkToContent = false } = props const DEFAULT_CONTAINER_STYLE = css` padding: ${SPACING.spacing2} ${SPACING.spacing8}; border-radius: ${BORDERS.borderRadius4}; + width: ${shrinkToContent ? FLEX_MAX_CONTENT : 'none'}; @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { border-radius: ${BORDERS.borderRadius8}; padding: ${SPACING.spacing8} ${SPACING.spacing12}; diff --git a/components/src/atoms/buttons/EmptySelectorButton.tsx b/components/src/atoms/buttons/EmptySelectorButton.tsx index 42e8822fc35..da34a8ba710 100644 --- a/components/src/atoms/buttons/EmptySelectorButton.tsx +++ b/components/src/atoms/buttons/EmptySelectorButton.tsx @@ -1,27 +1,18 @@ import styled from 'styled-components' import { Flex } from '../../primitives' import { + ALIGN_CENTER, CURSOR_DEFAULT, CURSOR_POINTER, + FLEX_MAX_CONTENT, Icon, - SPACING, - StyledText, JUSTIFY_CENTER, JUSTIFY_START, - ALIGN_CENTER, - FLEX_MAX_CONTENT, + SPACING, + StyledText, } from '../../index' -import { - black90, - blue30, - blue50, - grey30, - grey40, - white, -} from '../../helix-design-system/colors' -import { borderRadius8 } from '../../helix-design-system/borders' +import { BORDERS, COLORS } from '../../helix-design-system' import type { IconName } from '../../index' - interface EmptySelectorButtonProps { onClick: () => void text: string @@ -41,10 +32,9 @@ export function EmptySelectorButton( ` border: none; width: ${FLEX_MAX_CONTENT}; height: ${FLEX_MAX_CONTENT}; - cursor: ${({ disabled }) => (disabled ? CURSOR_DEFAULT : CURSOR_POINTER)}; + cursor: ${CURSOR_POINTER}; + background-color: ${COLORS.blue30}; + border-radius: ${BORDERS.borderRadius8}; + &:focus-visible { - outline: 2px solid ${white}; - box-shadow: 0 0 0 4px ${blue50}; - border-radius: ${borderRadius8}; + outline: 2px solid ${COLORS.white}; + box-shadow: 0 0 0 4px ${COLORS.blue50}; + border-radius: ${BORDERS.borderRadius8}; + } + &:hover { + background-color: ${COLORS.blue35}; + } + &:disabled { + background-color: ${COLORS.grey20}; + cursor: ${CURSOR_DEFAULT}; } ` diff --git a/components/src/atoms/buttons/RadioButton.tsx b/components/src/atoms/buttons/RadioButton.tsx index f960987f67f..a4d1a769144 100644 --- a/components/src/atoms/buttons/RadioButton.tsx +++ b/components/src/atoms/buttons/RadioButton.tsx @@ -212,5 +212,6 @@ const SettingButtonLabel = styled.label` -webkit-box-orient: ${({ maxLines }) => maxLines != null ? 'vertical' : 'none'}; word-wrap: break-word; + word-break: break-all; } ` diff --git a/components/src/hardware-sim/ProtocolDeck/utils/getModulesInSlots.ts b/components/src/hardware-sim/ProtocolDeck/utils/getModulesInSlots.ts index 612759b3d01..0d9da4b48bd 100644 --- a/components/src/hardware-sim/ProtocolDeck/utils/getModulesInSlots.ts +++ b/components/src/hardware-sim/ProtocolDeck/utils/getModulesInSlots.ts @@ -2,7 +2,6 @@ import { SPAN7_8_10_11_SLOT, getModuleDef2, getLoadedLabwareDefinitionsByUri, - NON_USER_ADDRESSABLE_LABWARE, } from '@opentrons/shared-data' import type { CompletedProtocolAnalysis, @@ -37,8 +36,7 @@ export const getModulesInSlots = ( commands .filter( (command): command is LoadLabwareRunTimeCommand => - command.commandType === 'loadLabware' && - !NON_USER_ADDRESSABLE_LABWARE.includes(command.params.loadName) + command.commandType === 'loadLabware' ) .find( (command: LoadLabwareRunTimeCommand) => diff --git a/hardware-testing/hardware_testing/production_qc/pipette_assembly_qc_ot3/__main__.py b/hardware-testing/hardware_testing/production_qc/pipette_assembly_qc_ot3/__main__.py index 90637e81540..6be7cc92fab 100644 --- a/hardware-testing/hardware_testing/production_qc/pipette_assembly_qc_ot3/__main__.py +++ b/hardware-testing/hardware_testing/production_qc/pipette_assembly_qc_ot3/__main__.py @@ -119,6 +119,7 @@ class TestConfig: num_trials: int droplet_wait_seconds: int simulate: bool + skip_all_pressure: bool @dataclass @@ -700,9 +701,12 @@ async def _test_for_leak( accumulate_raw_data_cb ), "pressure fixture requires recording data to disk" await _move_to_fixture(api, mount) - test_passed = await _fixture_check_pressure( - api, mount, test_config, fixture, write_cb, accumulate_raw_data_cb - ) + if not test_config.skip_all_pressure: + test_passed = await _fixture_check_pressure( + api, mount, test_config, fixture, write_cb, accumulate_raw_data_cb + ) + else: + test_passed = True else: await _pick_up_tip_for_tip_volume(api, mount, tip_volume=tip_volume) await _move_to_reservoir_liquid(api, mount) @@ -1129,7 +1133,9 @@ async def _read_pressure(_sensor_id: SensorId) -> float: return all(results) -async def _test_diagnostics(api: OT3API, mount: OT3Mount, write_cb: Callable) -> bool: +async def _test_diagnostics( + api: OT3API, mount: OT3Mount, write_cb: Callable, cfg: TestConfig +) -> bool: # ENVIRONMENT SENSOR environment_pass = await _test_diagnostics_environment(api, mount, write_cb) print(f"environment: {_bool_to_pass_fail(environment_pass)}") @@ -1146,9 +1152,14 @@ async def _test_diagnostics(api: OT3API, mount: OT3Mount, write_cb: Callable) -> print(f"capacitance: {_bool_to_pass_fail(capacitance_pass)}") write_cb(["diagnostics-capacitance", _bool_to_pass_fail(capacitance_pass)]) # PRESSURE - pressure_pass = await _test_diagnostics_pressure(api, mount, write_cb) - print(f"pressure: {_bool_to_pass_fail(pressure_pass)}") + if not cfg.skip_all_pressure: + pressure_pass = await _test_diagnostics_pressure(api, mount, write_cb) + print(f"pressure: {_bool_to_pass_fail(pressure_pass)}") + else: + print("Skipping pressure") + pressure_pass = True write_cb(["diagnostics-pressure", _bool_to_pass_fail(pressure_pass)]) + return environment_pass and pressure_pass and encoder_pass and capacitance_pass @@ -1674,7 +1685,9 @@ async def _main(test_config: TestConfig) -> None: # noqa: C901 if not test_config.skip_diagnostics: await api.move_to(mount, hover_over_slot_3) await api.move_rel(mount, Point(z=-20)) - test_passed = await _test_diagnostics(api, mount, csv_cb.write) + test_passed = await _test_diagnostics( + api, mount, csv_cb.write, test_config + ) await api.retract(mount) csv_cb.results("diagnostics", test_passed) if not test_config.skip_plunger: @@ -1806,6 +1819,7 @@ async def _main(test_config: TestConfig) -> None: # noqa: C901 arg_parser.add_argument("--skip-plunger", action="store_true") arg_parser.add_argument("--skip-tip-presence", action="store_true") arg_parser.add_argument("--skip-liquid-probe", action="store_true") + arg_parser.add_argument("--skip-all-pressure", action="store_true") arg_parser.add_argument("--fixture-side", choices=["left", "right"], default="left") arg_parser.add_argument("--port", type=str, default="") arg_parser.add_argument("--num-trials", type=int, default=2) @@ -1841,11 +1855,11 @@ async def _main(test_config: TestConfig) -> None: # noqa: C901 _cfg = TestConfig( operator_name=operator, skip_liquid=args.skip_liquid, - skip_fixture=args.skip_fixture, + skip_fixture=args.skip_fixture or args.skip_all_pressure, skip_diagnostics=args.skip_diagnostics, skip_plunger=args.skip_plunger, skip_tip_presence=args.skip_tip_presence, - skip_liquid_probe=args.skip_liquid_probe, + skip_liquid_probe=args.skip_liquid_probe or args.skip_all_pressure, fixture_port=args.port, fixture_side=args.fixture_side, fixture_aspirate_sample_count=args.aspirate_sample_count, @@ -1859,6 +1873,7 @@ async def _main(test_config: TestConfig) -> None: # noqa: C901 num_trials=args.num_trials, droplet_wait_seconds=args.wait, simulate=args.simulate, + skip_all_pressure=args.skip_all_pressure, ) # NOTE: overwrite default aspirate sample-count from user's input # FIXME: this value is being set in a few places, maybe there's a way to clean this up diff --git a/hardware-testing/hardware_testing/scripts/ABRAsairScript.py b/hardware-testing/hardware_testing/scripts/ABRAsairScript.py index 41c70ed35a2..710d3c17578 100644 --- a/hardware-testing/hardware_testing/scripts/ABRAsairScript.py +++ b/hardware-testing/hardware_testing/scripts/ABRAsairScript.py @@ -2,6 +2,7 @@ import sys import paramiko as pmk import time +import json import multiprocessing from typing import Optional, List, Any @@ -69,11 +70,10 @@ def run(file_name: str) -> List[Any]: robot_ips = [] robot_names = [] with open(file_name) as file: - for line in file.readlines(): - info = line.split(",") - if "Y" in info[2]: - robot_ips.append(info[0]) - robot_names.append(info[1]) + file_dict = json.load(file) + robot_dict = file_dict.get("ip_address_list") + robot_ips = list(robot_dict.keys()) + robot_names = list(robot_dict.values()) print("Executing Script on All Robots:") # Launch the processes for each robot. processes = [] @@ -89,10 +89,8 @@ def run(file_name: str) -> List[Any]: # Wait for all processes to finish. file_name = sys.argv[1] processes = run(file_name) - for process in processes: process.start() time.sleep(20) - for process in processes: process.join() diff --git a/hardware/opentrons_hardware/firmware_bindings/constants.py b/hardware/opentrons_hardware/firmware_bindings/constants.py index ecdc8ae8c64..77b1dce5b3e 100644 --- a/hardware/opentrons_hardware/firmware_bindings/constants.py +++ b/hardware/opentrons_hardware/firmware_bindings/constants.py @@ -463,3 +463,4 @@ class GripperJawState(int, Enum): force_controlling_home = 0x1 force_controlling = 0x2 position_controlling = 0x3 + stopped = 0x4 diff --git a/hardware/opentrons_hardware/hardware_control/motor_position_status.py b/hardware/opentrons_hardware/hardware_control/motor_position_status.py index 90319764922..1ce9bbe3ce5 100644 --- a/hardware/opentrons_hardware/hardware_control/motor_position_status.py +++ b/hardware/opentrons_hardware/hardware_control/motor_position_status.py @@ -152,11 +152,7 @@ def _listener_filter(arbitration_id: ArbitrationId) -> bool: log.warning("Update motor position estimation timed out") raise CommandTimedOutError( "Update motor position estimation timed out", - detail={ - "missing-nodes": ", ".join( - node.name for node in set(nodes).difference(set(data)) - ) - }, + detail={"missing-node": node.name}, ) return data diff --git a/hardware/tests/opentrons_hardware/hardware_control/test_motion_plan.py b/hardware/tests/opentrons_hardware/hardware_control/test_motion_plan.py index 857c0d08f92..64ed76a6856 100644 --- a/hardware/tests/opentrons_hardware/hardware_control/test_motion_plan.py +++ b/hardware/tests/opentrons_hardware/hardware_control/test_motion_plan.py @@ -2,7 +2,7 @@ import numpy as np from hypothesis import given, assume, strategies as st from hypothesis.extra import numpy as hynp -from typing import Iterator, List, Tuple +from typing import Iterator, List, Tuple, Dict from opentrons_hardware.hardware_control.motion_planning import move_manager from opentrons_hardware.hardware_control.motion_planning.types import ( @@ -210,3 +210,60 @@ def test_close_move_plan( ) assert converged, f"Failed to converge: {blend_log}" + + +def test_pipette_high_speed_motion() -> None: + """Test that updated motion constraint doesn't get overridden by motion planning.""" + origin: Dict[str, int] = { + "X": 499, + "Y": 499, + "Z": 499, + "A": 499, + "B": 499, + "C": 499, + } + target_list = [] + axis_kinds = ["X", "Y", "Z", "A", "B", "C"] + constraints: SystemConstraints[str] = {} + for axis_kind in axis_kinds: + constraints[axis_kind] = AxisConstraints.build( + max_acceleration=500, + max_speed_discont=500, + max_direction_change_speed_discont=500, + max_speed=500, + ) + origin_mapping: Dict[str, float] = {axis_kind: float(origin[axis_kind])} + target_list.append(MoveTarget.build(origin_mapping, 500)) + + set_axis_kind = "A" + dummy_em_pipette_max_speed = 90.0 + manager = move_manager.MoveManager(constraints=constraints) + + new_axis_constraint = AxisConstraints.build( + max_acceleration=float(constraints[set_axis_kind].max_acceleration), + max_speed_discont=float(constraints[set_axis_kind].max_speed_discont), + max_direction_change_speed_discont=float( + constraints[set_axis_kind].max_direction_change_speed_discont + ), + max_speed=90.0, + ) + new_constraints = {} + + for axis_kind in constraints.keys(): + if axis_kind == set_axis_kind: + new_constraints[axis_kind] = new_axis_constraint + else: + new_constraints[axis_kind] = constraints[axis_kind] + + manager.update_constraints(constraints=new_constraints) + converged, blend_log = manager.plan_motion( + origin=origin, + target_list=target_list, + iteration_limit=20, + ) + for move in blend_log[0]: + unit_vector = move.unit_vector + for block in move.blocks: + top_set_axis_speed = unit_vector[set_axis_kind] * block.final_speed + if top_set_axis_speed != 0: + assert abs(top_set_axis_speed) == dummy_em_pipette_max_speed diff --git a/protocol-designer/cypress/support/commands.ts b/protocol-designer/cypress/support/commands.ts index b97c11f2bd2..3f9ffd8ddd8 100644 --- a/protocol-designer/cypress/support/commands.ts +++ b/protocol-designer/cypress/support/commands.ts @@ -45,9 +45,9 @@ export const content = { charSet: 'UTF-8', header: 'Protocol Designer', welcome: 'Welcome to Protocol Designer!', - appSettings: 'App settings', + appSettings: 'App Info', privacy: 'Privacy', - shareSessions: 'Share sessions with Opentrons', + shareSessions: 'Share analytics with Opentrons', } export const locators = { diff --git a/protocol-designer/src/assets/images/onboarding_animation_1.webm b/protocol-designer/src/assets/images/onboarding_animation_1.webm new file mode 100644 index 00000000000..6eed789cb61 Binary files /dev/null and b/protocol-designer/src/assets/images/onboarding_animation_1.webm differ diff --git a/protocol-designer/src/assets/images/onboarding_animation_2.webm b/protocol-designer/src/assets/images/onboarding_animation_2.webm new file mode 100644 index 00000000000..7dbc51c26ad Binary files /dev/null and b/protocol-designer/src/assets/images/onboarding_animation_2.webm differ diff --git a/protocol-designer/src/assets/images/onboarding_animation_3.webm b/protocol-designer/src/assets/images/onboarding_animation_3.webm new file mode 100644 index 00000000000..19d29e2b939 Binary files /dev/null and b/protocol-designer/src/assets/images/onboarding_animation_3.webm differ diff --git a/protocol-designer/src/assets/images/onboarding_animation_4.webm b/protocol-designer/src/assets/images/onboarding_animation_4.webm new file mode 100644 index 00000000000..d59f86faf45 Binary files /dev/null and b/protocol-designer/src/assets/images/onboarding_animation_4.webm differ diff --git a/protocol-designer/src/assets/images/onboarding_animation_5.webm b/protocol-designer/src/assets/images/onboarding_animation_5.webm new file mode 100644 index 00000000000..4fc1580c4f8 Binary files /dev/null and b/protocol-designer/src/assets/images/onboarding_animation_5.webm differ diff --git a/protocol-designer/src/assets/images/onboarding_animation_6.webm b/protocol-designer/src/assets/images/onboarding_animation_6.webm new file mode 100644 index 00000000000..ea00e3fcdb1 Binary files /dev/null and b/protocol-designer/src/assets/images/onboarding_animation_6.webm differ diff --git a/protocol-designer/src/assets/images/placeholder_image_delete.png b/protocol-designer/src/assets/images/placeholder_image_delete.png deleted file mode 100644 index f429a3862dc..00000000000 Binary files a/protocol-designer/src/assets/images/placeholder_image_delete.png and /dev/null differ diff --git a/protocol-designer/src/assets/localization/en/create_new_protocol.json b/protocol-designer/src/assets/localization/en/create_new_protocol.json index 269c252dd39..f2e132728a5 100644 --- a/protocol-designer/src/assets/localization/en/create_new_protocol.json +++ b/protocol-designer/src/assets/localization/en/create_new_protocol.json @@ -11,7 +11,7 @@ "edit": "Edit", "fixtures_added": "Fixtures added", "fixtures_replace": "Fixtures replace standard deck slots and let you add functionality to your Flex.", - "incompatible_tip_body": "Protocol Designer only accepts custom JSON labware definitions made with our Labware Creator. Upload a valid file to continue.", + "incompatible_tip_body": "Using a pipette with an incompatible tip rack may result reduce pipette accuracy and collisions. We strongly recommend that you do not pair a pipette with an incompatible tip rack.", "incompatible_tips": "Incompatible tips", "labware_name": "Labware name", "left_right": "Left + Right", diff --git a/protocol-designer/src/assets/localization/en/feature_flags.json b/protocol-designer/src/assets/localization/en/feature_flags.json index 74c524de0e0..c39e10a8785 100644 --- a/protocol-designer/src/assets/localization/en/feature_flags.json +++ b/protocol-designer/src/assets/localization/en/feature_flags.json @@ -25,7 +25,7 @@ "description": "You can choose which tip to pick up and where to drop tip." }, "OT_PD_ENABLE_HOT_KEYS_DISPLAY": { - "title": "Timeline editing tips", - "description": "Show tips for working with steps next to the protocol timeline" + "title": "Timeline editing guidance", + "description": "Show information about working with steps next to the protocol timeline" } } diff --git a/protocol-designer/src/assets/localization/en/protocol_steps.json b/protocol-designer/src/assets/localization/en/protocol_steps.json index 6105b29b24d..50c6f7879f8 100644 --- a/protocol-designer/src/assets/localization/en/protocol_steps.json +++ b/protocol-designer/src/assets/localization/en/protocol_steps.json @@ -27,7 +27,7 @@ "duplicate": "Duplicate step", "edit_step": "Edit step", "engage_height": "Engage height", - "final_deck_state": "Final deck state", + "ending_deck": "Ending deck", "flow_type_title": "{{type}} flow rate", "from": "from", "heater_shaker": { @@ -104,7 +104,7 @@ "shake": "Shake", "single": "Single path", "speed": "Speed", - "starting_deck_state": "Starting deck state", + "starting_deck": "Starting deck", "step_substeps": "{{stepType}} details", "temperature": "Temperature", "temperature_module": { @@ -120,8 +120,8 @@ "substep_settings": "Set block temperature tofor", "thermocycler_profile": { "end_hold": { - "block": "End at thermocycler block", - "lid_position": "Thermocycler lid" + "block": "End with block at", + "lid_position": "and lid" }, "lid_temperature": "and lid temperature at", "volume": "Run thermocycler profile with" diff --git a/protocol-designer/src/assets/localization/en/shared.json b/protocol-designer/src/assets/localization/en/shared.json index 3a6545622a5..58579da36ff 100644 --- a/protocol-designer/src/assets/localization/en/shared.json +++ b/protocol-designer/src/assets/localization/en/shared.json @@ -3,7 +3,7 @@ "agree": "Agree", "amount": "Amount:", "analytics_tracking": "I consent to analytics tracking:", - "app_settings": "App settings", + "app_info": "App Info", "ask_for_labware_overwrite": "Duplicate labware name", "back": "Back", "cancel": "Cancel", @@ -17,7 +17,7 @@ "create_a_protocol": "Create a protocol", "create_new": "Create new", "destination_well": "Destination Well", - "developer_ff": "Developer feature flags", + "developer_ff": "Developer Feature Flags", "done": "Done", "edit_existing": "Edit existing protocol", "edit_instruments": "Edit Instruments", @@ -84,7 +84,7 @@ }, "message_exact_labware_match": "This labware is identical to one you have already uploaded.", "message_invalid_json_file": "Protocol Designer only accepts custom JSON labware definitions made with our Labware Creator", - "message_not_json": "Protocol Designer only accepts JSON files.", + "message_not_json": "Protocol Designer only accepts custom JSON labware definitions made with our Labware Creator. Upload a valid file to continue.", "message_only_tiprack": "This labware definition is not a tip rack.", "message_uses_standard_namespace": "This labware definition uses the Opentrons standard labware namespace. Change the namespace if it is custom, or use the standard labware in your protocol.", "mismatched": "The new labware has a different arrangement of wells than the labware it is replacing. Clicking Overwrite will deselect all wells in any existing steps that use this labware. You will have to edit each of those steps and select new wells.", @@ -96,7 +96,7 @@ "no-code-required": "The easiest way to automate liquid handling on your Opentrons robot. No code required.", "no": "No", "none": "None", - "not_json": "Incompatible file type", + "not_json": "Invalid file type", "one_channel": "1-Channel", "only_tiprack": "Incompatible file type", "opentrons_flex": "Opentrons Flex", @@ -116,8 +116,8 @@ "release_notes": "Release notes", "reload_app": "Reload app", "remove": "remove", - "reset_hints_and_tips": "Reset all hints and tips notifications", - "reset_hints": "Reset hints", + "show_hints_and_tips": "Show all hints and tips notifications again", + "reset": "Reset", "reset_to_default": "Reset to default", "resize_your_browser": "Resize your browser to at least 768px wide and 650px tall to continue editing your protocol", "review_our_privacy_policy": "You can adjust this setting at any time by clicking on the settings icon. Find detailed information in our privacy policy.", @@ -127,7 +127,7 @@ "settings": "Settings", "shared_display_name": "Shared display name: ", "shared_load_name": "Shared load name: ", - "shared_sessions": "Share sessions with Opentrons", + "shared_analytics": "Share analytics with Opentrons", "shares_name": "This labware has the same load name or display name as {{customOrStandard}}, which is already in this protocol.", "slot_detail": "Slot Detail", "software_manual": "Software manual", @@ -147,14 +147,14 @@ "tip_position": "Edit {{prefix}} tip position", "trashBin": "Trash Bin", "updated_protocol_designer": "We've updated Protocol Designer!", - "user_settings": "User settings", + "user_settings": "User Settings", "uses_standard_namespace": "Opentrons verified labware", "version": "Version {{version}}", "view_release_notes": "View release notes", "warning": "WARNING:", "wasteChute": "Waste chute", "wasteChuteAndStagingArea": "Waste chute and staging area slot", - "we_are_improving": "In order to improve our products, Opentrons would like to collect data related to your use of Protocol Designer. With your consent, Opentrons will collect and store analytics and session data, including through the use of cookies and similar technologies, solely for the purpose enhancing our products. Find detailed information in our privacy policy. By using Protocol Designer, you consent to the Opentrons EULA.", + "we_are_improving": "Help Opentrons improve its products and services by automatically sending anonymous diagnostics and usage data.", "welcome": "Welcome to Protocol Designer!", "yes": "Yes", "your_screen_is_too_small": "Your browser size is too small" diff --git a/protocol-designer/src/assets/localization/en/starting_deck_state.json b/protocol-designer/src/assets/localization/en/starting_deck_state.json index fcf88c2866e..0522cc1291a 100644 --- a/protocol-designer/src/assets/localization/en/starting_deck_state.json +++ b/protocol-designer/src/assets/localization/en/starting_deck_state.json @@ -1,4 +1,7 @@ { + "__end__": "Ending deck", + "__initial_setup__": "Starting deck", + "__presaved_step__": "Unsaved step", "adapter_compatible_lab": "Adapter compatible labware", "adapter": "Adapters", "add_fixture": "Add a fixture", @@ -13,7 +16,7 @@ "clear_labware": "Clear labware", "clear_slot": "Clear slot", "clear": "Clear", - "command_click_to_multi_select": "Command + Click for multi-select", + "command_click_to_multi_select": "^/⌘ + click to select multiple", "convert_gen1_to_gen2": "To convert engage heights from GEN1 to GEN2, divide your engage height by 2.", "convert_gen2_to_gen1": "To convert engage heights from GEN2 to GEN1, multiply your engage height by 2.", "custom": "Custom labware definitions", @@ -48,7 +51,7 @@ "read_more_gen1_gen2": "Read more about the differences between GEN1 and GEN2 Magnetic Modules", "rename_lab": "Rename labware", "reservoir": "Reservoirs", - "shift_click_to_select_all": "Shift + Click to select all", + "shift_click_to_select_range": "⇧ + click to select range", "starting_deck_state": "Starting deck state", "tc_slots_occupied_flex": "The Thermocycler needs slots A1 and B1. Slot A1 is occupied", "tc_slots_occupied_ot2": "The Thermocycler needs slots 7, 8, 10, and 11. One or more of those slots is occupied", diff --git a/protocol-designer/src/atoms/constants.ts b/protocol-designer/src/atoms/constants.ts index e04701a7639..620a3d10dbc 100644 --- a/protocol-designer/src/atoms/constants.ts +++ b/protocol-designer/src/atoms/constants.ts @@ -1,11 +1,8 @@ -import styled, { css } from 'styled-components' +import { css } from 'styled-components' import { - BORDERS, COLORS, DIRECTION_COLUMN, OVERFLOW_HIDDEN, - SPACING, - TYPOGRAPHY, } from '@opentrons/components' import type { FlattenSimpleInterpolation } from 'styled-components' @@ -35,40 +32,3 @@ export const COLUMN_STYLE = css` min-width: calc((${MIN_OVERVIEW_WIDTH} - ${COLUMN_GRID_GAP}) * 0.5); flex: 1; ` - -export const DescriptionField = styled.textarea` - min-height: 5rem; - width: 100%; - border: 1px ${BORDERS.styleSolid} ${COLORS.grey50}; - border-radius: ${BORDERS.borderRadius4}; - padding: ${SPACING.spacing8}; - font-size: ${TYPOGRAPHY.fontSizeP}; - resize: none; - - &:active:enabled { - border: 1px ${BORDERS.styleSolid} ${COLORS.blue50}; - } - - &:hover { - border: 1px ${BORDERS.styleSolid} ${COLORS.grey60}; - } - - &:focus-visible { - border: 1px ${BORDERS.styleSolid} ${COLORS.grey55}; - outline: 2px ${BORDERS.styleSolid} ${COLORS.blue50}; - outline-offset: 2px; - } - - &:focus-within { - border: 1px ${BORDERS.styleSolid} ${COLORS.blue50}; - } - - &:disabled { - border: 1px ${BORDERS.styleSolid} ${COLORS.grey30}; - } - input[type='number']::-webkit-inner-spin-button, - input[type='number']::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; - } -` diff --git a/protocol-designer/src/labware-ingred/actions/thunks.ts b/protocol-designer/src/labware-ingred/actions/thunks.ts index dedfae883d8..38cccb252fb 100644 --- a/protocol-designer/src/labware-ingred/actions/thunks.ts +++ b/protocol-designer/src/labware-ingred/actions/thunks.ts @@ -73,7 +73,6 @@ export const createContainer: ( args.slot || getNextAvailableDeckSlot(initialDeckSetup, robotType, labwareDef) const isTiprack = getIsTiprack(labwareDef) - if (slot) { const id = `${uuid()}:${args.labwareDefURI}` const adapterId = diff --git a/protocol-designer/src/labware-ingred/utils.ts b/protocol-designer/src/labware-ingred/utils.ts index d4c6dc5e1bf..377dff50eb5 100644 --- a/protocol-designer/src/labware-ingred/utils.ts +++ b/protocol-designer/src/labware-ingred/utils.ts @@ -1,5 +1,6 @@ import { FIXED_TRASH_ID, + FLEX_MODULE_ADDRESSABLE_AREAS, getAreSlotsAdjacent, getDeckDefFromRobotType, getIsLabwareAboveHeight, @@ -7,6 +8,7 @@ import { MAX_LABWARE_HEIGHT_EAST_WEST_HEATER_SHAKER_MM, MOVABLE_TRASH_ADDRESSABLE_AREAS, OT2_ROBOT_TYPE, + THERMOCYCLER_MODULE_TYPE, WASTE_CHUTE_ADDRESSABLE_AREAS, } from '@opentrons/shared-data' import { COLUMN_4_SLOTS } from '@opentrons/step-generation' @@ -30,6 +32,16 @@ export function getNextAvailableDeckSlot( module => module.type === HEATERSHAKER_MODULE_TYPE )?.slot + const hasTC = Object.values(initialDeckSetup.modules).find( + module => module.type === THERMOCYCLER_MODULE_TYPE + ) + let moduleSlots = Object.values(initialDeckSetup.modules) + .filter(module => module.slot) + .map(mod => mod.slot) + if (hasTC) { + moduleSlots = [...moduleSlots, '8', '10', '11'] + } + return deckDef.locations.addressableAreas.find(slot => { const cutoutIds = Object.values(initialDeckSetup.additionalEquipmentOnDeck) .filter(ae => ae.name === 'stagingArea') @@ -47,12 +59,17 @@ export function getNextAvailableDeckSlot( MOVABLE_TRASH_ADDRESSABLE_AREAS.includes(slot.id) || WASTE_CHUTE_ADDRESSABLE_AREAS.includes(slot.id) || slot.id === FIXED_TRASH_ID + ) { + isSlotEmpty = false + } else if ( + moduleSlots.includes(slot.id) || + FLEX_MODULE_ADDRESSABLE_AREAS.includes(slot.id) ) { isSlotEmpty = false // return slot as full if slot is adjacent to heater-shaker for ot-2 and taller than 53mm } else if ( heaterShakerSlot != null && - deckDef.robot.model === OT2_ROBOT_TYPE && + robotType === OT2_ROBOT_TYPE && isSlotEmpty && labwareDefinition != null ) { diff --git a/protocol-designer/src/organisms/DefineLiquidsModal/index.tsx b/protocol-designer/src/organisms/DefineLiquidsModal/index.tsx index fb1d775b702..35bf74003a8 100644 --- a/protocol-designer/src/organisms/DefineLiquidsModal/index.tsx +++ b/protocol-designer/src/organisms/DefineLiquidsModal/index.tsx @@ -5,8 +5,10 @@ import { SketchPicker } from 'react-color' import { yupResolver } from '@hookform/resolvers/yup' import * as Yup from 'yup' import { Controller, useForm } from 'react-hook-form' +import styled from 'styled-components' import { DEFAULT_LIQUID_COLORS } from '@opentrons/shared-data' import { + BORDERS, Btn, COLORS, DIRECTION_COLUMN, @@ -28,7 +30,7 @@ import * as labwareIngredActions from '../../labware-ingred/actions' import { selectors as labwareIngredSelectors } from '../../labware-ingred/selectors' import { swatchColors } from '../../components/swatchColors' import { HandleEnter } from '../../atoms/HandleEnter' -import { DescriptionField, LINE_CLAMP_TEXT_STYLE } from '../../atoms' +import { LINE_CLAMP_TEXT_STYLE } from '../../atoms' import type { ColorResult, RGBColor } from 'react-color' import type { ThunkDispatch } from 'redux-thunk' @@ -310,3 +312,13 @@ export function DefineLiquidsModal( ) } + +export const DescriptionField = styled.textarea` + min-height: 5rem; + width: 100%; + border: 1px ${BORDERS.styleSolid} ${COLORS.grey50}; + border-radius: ${BORDERS.borderRadius4}; + padding: ${SPACING.spacing8}; + font-size: ${TYPOGRAPHY.fontSizeP}; + resize: none; +` diff --git a/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx b/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx index c986c1ce9ba..5aac9eeb3f1 100644 --- a/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx +++ b/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx @@ -89,7 +89,7 @@ export function EditInstrumentsModal( ): JSX.Element { const { onClose } = props const dispatch = useDispatch>() - const { t } = useTranslation([ + const { i18n, t } = useTranslation([ 'create_new_protocol', 'protocol_overview', 'shared', @@ -347,7 +347,7 @@ export function EditInstrumentsModal( desktopStyle="bodyDefaultRegular" color={COLORS.grey60} > - {t('gripper')} + {i18n.format(t('gripper'), 'capitalize')} - - {allowAllTipracks - ? t('show_default_tips') - : t('show_all_tips')} - + + + {allowAllTipracks + ? t('show_default_tips') + : t('show_all_tips')} + + {' '} )} @@ -594,4 +596,7 @@ const StyledLabel = styled.label` input[type='file'] { display: none; } + &:hover { + color: ${COLORS.blue50}; + } ` diff --git a/protocol-designer/src/organisms/IncompatibleTipsModal/__tests__/IncompatibleTipsModal.test.tsx b/protocol-designer/src/organisms/IncompatibleTipsModal/__tests__/IncompatibleTipsModal.test.tsx index 96732965dea..2a1a8cf7e4e 100644 --- a/protocol-designer/src/organisms/IncompatibleTipsModal/__tests__/IncompatibleTipsModal.test.tsx +++ b/protocol-designer/src/organisms/IncompatibleTipsModal/__tests__/IncompatibleTipsModal.test.tsx @@ -26,7 +26,7 @@ describe('IncompatibleTipsModal', () => { render(props) screen.getByText('Incompatible tips') screen.getByText( - 'Protocol Designer only accepts custom JSON labware definitions made with our Labware Creator. Upload a valid file to continue.' + 'Using a pipette with an incompatible tip rack may result reduce pipette accuracy and collisions. We strongly recommend that you do not pair a pipette with an incompatible tip rack.' ) fireEvent.click(screen.getByText('Show more tip types')) expect(vi.mocked(setFeatureFlags)).toHaveBeenCalled() diff --git a/protocol-designer/src/organisms/LabwareUploadModal/_tests__/LabwareUploadModal.test.tsx b/protocol-designer/src/organisms/LabwareUploadModal/_tests__/LabwareUploadModal.test.tsx index 0a7aba37a2f..4d3277453b6 100644 --- a/protocol-designer/src/organisms/LabwareUploadModal/_tests__/LabwareUploadModal.test.tsx +++ b/protocol-designer/src/organisms/LabwareUploadModal/_tests__/LabwareUploadModal.test.tsx @@ -24,10 +24,12 @@ describe('LabwareUploadModal', () => { it('renders modal for not json', () => { render() - screen.getByText('Protocol Designer only accepts JSON files.') - screen.getByText('Incompatible file type') + screen.getByText( + 'Protocol Designer only accepts custom JSON labware definitions made with our Labware Creator. Upload a valid file to continue.' + ) + screen.getByText('Invalid file type') fireEvent.click( - screen.getByTestId('ModalHeader_icon_close_Incompatible file type') + screen.getByTestId('ModalHeader_icon_close_Invalid file type') ) expect(vi.mocked(dismissLabwareUploadMessage)).toHaveBeenCalled() }) diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/AddMetadata.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/AddMetadata.tsx index afb8e83e9c7..fcc9956ad6b 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/AddMetadata.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/AddMetadata.tsx @@ -1,16 +1,18 @@ import { useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' +import styled from 'styled-components' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import { + BORDERS, COLORS, DIRECTION_COLUMN, Flex, InputField, SPACING, StyledText, + TYPOGRAPHY, } from '@opentrons/components' -import { DescriptionField } from '../../atoms' import { HandleEnter } from '../../atoms/HandleEnter' import { analyticsEvent } from '../../analytics/actions' import { ONBOARDING_FLOW_DURATION_EVENT } from '../../analytics/constants' @@ -92,3 +94,13 @@ export function AddMetadata(props: AddMetadataProps): JSX.Element | null { ) } + +export const DescriptionField = styled.textarea` + min-height: 5rem; + width: 100%; + border: 1px ${BORDERS.styleSolid} ${COLORS.grey50}; + border-radius: ${BORDERS.borderRadius4}; + padding: ${SPACING.spacing8}; + font-size: ${TYPOGRAPHY.fontSizeP}; + resize: none; +` diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx index 3f48e08e6bd..b3c4e691746 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx @@ -14,6 +14,7 @@ import { Box, Btn, Checkbox, + COLORS, CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, @@ -399,14 +400,16 @@ export function SelectPipettes(props: WizardTileProps): JSX.Element | null { TYPOGRAPHY.textDecorationUnderline } > - - {allowAllTipracks - ? t('show_default_tips') - : t('show_all_tips')} - + + + {allowAllTipracks + ? t('show_default_tips') + : t('show_all_tips')} + + )} @@ -557,4 +560,7 @@ const StyledLabel = styled.label` input[type='file'] { display: none; } + &:hover { + color: ${COLORS.blue50}; + } ` diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/WizardBody.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/WizardBody.tsx index bfac8f51a19..442f6d0e4ed 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/WizardBody.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/WizardBody.tsx @@ -1,6 +1,6 @@ -import type * as React from 'react' import { useTranslation } from 'react-i18next' -import styled from 'styled-components' +import { css } from 'styled-components' +import { useState, useLayoutEffect } from 'react' import { ALIGN_CENTER, ALIGN_END, @@ -18,7 +18,12 @@ import { TYPOGRAPHY, useHoverTooltip, } from '@opentrons/components' -import temporaryImg from '../../assets/images/placeholder_image_delete.png' +import one from '../../assets/images/onboarding_animation_1.webm' +import two from '../../assets/images/onboarding_animation_2.webm' +import three from '../../assets/images/onboarding_animation_3.webm' +import four from '../../assets/images/onboarding_animation_4.webm' +import five from '../../assets/images/onboarding_animation_5.webm' +import six from '../../assets/images/onboarding_animation_6.webm' import { BUTTON_LINK_STYLE } from '../../atoms' interface WizardBodyProps { @@ -29,9 +34,18 @@ interface WizardBodyProps { disabled?: boolean goBack?: () => void subHeader?: string - imgSrc?: string tooltipOnDisabled?: string } + +const ONBOARDING_ANIMATIONS: Record = { + 1: one, + 2: two, + 3: three, + 4: four, + 5: five, + 6: six, +} + export function WizardBody(props: WizardBodyProps): JSX.Element { const { stepNumber, @@ -41,13 +55,26 @@ export function WizardBody(props: WizardBodyProps): JSX.Element { subHeader, proceed, disabled = false, - imgSrc, tooltipOnDisabled, } = props const { t } = useTranslation('shared') const [targetProps, tooltipProps] = useHoverTooltip({ placement: 'top', }) + const [asset, setAsset] = useState(null) + const [loaded, setLoaded] = useState(false) + + useLayoutEffect(() => { + const videoAsset = ONBOARDING_ANIMATIONS[stepNumber] + setLoaded(false) + setAsset(videoAsset) + const timeout = setTimeout(() => { + setLoaded(true) + }, 100) + return () => { + clearTimeout(timeout) + } + }, [stepNumber]) return ( - - + + ) } - -const StyledImg = styled.img` - border-radius: ${BORDERS.borderRadius16}; - max-height: 844px; -` diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/__tests__/WizardBody.test.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/__tests__/WizardBody.test.tsx index 085e2e76efc..f0493707774 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/__tests__/WizardBody.test.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/__tests__/WizardBody.test.tsx @@ -37,6 +37,6 @@ describe('WizardBody', () => { expect(props.proceed).toHaveBeenCalled() fireEvent.click(screen.getByRole('button', { name: 'Go back' })) expect(props.goBack).toHaveBeenCalled() - screen.getByRole('img', { name: '' }) + screen.getByLabelText('onboarding animation for page 1') }) }) diff --git a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx index 921e9c1fc55..91e793f5005 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx @@ -195,15 +195,14 @@ export function DeckSetupContainer(props: DeckSetupTabType): JSX.Element { ) return ( - + - - {hoverSlot != null && - breakPointSize !== 'small' && - LEFT_SLOTS.includes(hoverSlot) ? ( - - ) : null} - + {zoomIn.slot == null ? ( + + {hoverSlot != null && + breakPointSize !== 'small' && + LEFT_SLOTS.includes(hoverSlot) ? ( + + ) : null} + + ) : null} {() => ( <> @@ -356,13 +357,15 @@ export function DeckSetupContainer(props: DeckSetupTabType): JSX.Element { )} - - {hoverSlot != null && - breakPointSize !== 'small' && - !LEFT_SLOTS.includes(hoverSlot) ? ( - - ) : null} - + {zoomIn.slot == null ? ( + + {hoverSlot != null && + breakPointSize !== 'small' && + !LEFT_SLOTS.includes(hoverSlot) ? ( + + ) : null} + + ) : null} {zoomIn.slot != null && zoomIn.cutout != null ? ( diff --git a/protocol-designer/src/pages/Designer/DeckSetup/utils.ts b/protocol-designer/src/pages/Designer/DeckSetup/utils.ts index 7a1c7c09be3..a288947365a 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/utils.ts +++ b/protocol-designer/src/pages/Designer/DeckSetup/utils.ts @@ -215,15 +215,15 @@ export function zoomInOnCoordinate(props: ZoomInOnCoordinateProps): string { const { x, y, deckDef } = props const [width, height] = [deckDef.dimensions[0], deckDef.dimensions[1]] - const zoomFactor = 0.6 + const zoomFactor = 0.55 const newWidth = width * zoomFactor const newHeight = height * zoomFactor // +125 and +50 to get the approximate center of the screen point - const newMinX = x - newWidth / 2 + 125 + const newMinX = x - newWidth / 2 + 20 const newMinY = y - newHeight / 2 + 50 - return `${newMinX} ${newMinY} ${newWidth} ${newHeight}` + return `${newMinX} ${newMinY} ${newWidth} ${newHeight + 70}` } export interface AnimateZoomProps { diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/BatchEditToolbox/BatchEditMixTools.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/BatchEditToolbox/BatchEditMixTools.tsx index 614fc880d5d..6f39f7ff632 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/BatchEditToolbox/BatchEditMixTools.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/BatchEditToolbox/BatchEditMixTools.tsx @@ -22,6 +22,7 @@ import { getBlowoutLocationOptionsForForm, getLabwareFieldForPositioningField, } from '../StepForm/utils' + import type { WellOrderOption } from '../../../../form-types' import type { FieldPropsByName } from '../StepForm/types' @@ -59,24 +60,18 @@ export function BatchEditMixTools(props: BatchEditMixToolsProps): JSX.Element { return pipetteId ? String(pipetteId) : null } - const getWellOrderFieldValue = ( - name: string - ): WellOrderOption | null | undefined => { - const val = propsForFields[name]?.value - if (val === 'l2r' || val === 'r2l' || val === 't2b' || val === 'b2t') { - return val - } else { - return null - } - } - return ( - - + + - + @@ -115,7 +116,7 @@ export function BatchEditMixTools(props: BatchEditMixToolsProps): JSX.Element { ) : null} @@ -159,6 +160,7 @@ export function BatchEditMixTools(props: BatchEditMixToolsProps): JSX.Element { options={getBlowoutLocationOptionsForForm({ stepType: 'mix', })} + padding="0" /> ) : null} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/BatchEditToolbox/BatchEditMoveLiquidTools.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/BatchEditToolbox/BatchEditMoveLiquidTools.tsx index b032bab56b3..561a926cc8f 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/BatchEditToolbox/BatchEditMoveLiquidTools.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/BatchEditToolbox/BatchEditMoveLiquidTools.tsx @@ -61,24 +61,18 @@ export function BatchEditMoveLiquidTools( const labwareId = propsForFields[labwareField]?.value return labwareId ? String(labwareId) : null } - const getWellOrderFieldValue = ( - name: string - ): WellOrderOption | null | undefined => { - const val = propsForFields[name]?.value - if (val === 'l2r' || val === 'r2l' || val === 't2b' || val === 'b2t') { - return val - } else { - return null - } - } return ( - + - + @@ -119,7 +113,7 @@ export function BatchEditMoveLiquidTools( @@ -227,6 +221,7 @@ export function BatchEditMoveLiquidTools( path: propsForFields.path.value as any, stepType: 'moveLiquid', })} + padding="0" /> ) : ( - - + + + + {stepSummaryContent != null ? ( - {stepSummaryContent} + + {stepSummaryContent} + ) : null} {stepDetails != null && stepDetails !== '' ? ( diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/MultichannelSubstep.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/MultichannelSubstep.tsx index 920ee3c0938..afd08e74e21 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/MultichannelSubstep.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/MultichannelSubstep.tsx @@ -17,13 +17,11 @@ import type { AdditionalEquipmentName } from '@opentrons/step-generation' import type { StepItemSourceDestRow, SubstepIdentifier, - WellIngredientNames, } from '../../../../steplist' interface MultichannelSubstepProps { trashName: AdditionalEquipmentName | null rowGroup: StepItemSourceDestRow[] - ingredNames: WellIngredientNames stepId: string substepIndex: number selectSubstep: (substepIdentifier: SubstepIdentifier) => void @@ -39,7 +37,6 @@ export function MultichannelSubstep( stepId, selectSubstep, substepIndex, - ingredNames, trashName, isSameLabware, } = props @@ -107,7 +104,6 @@ export function MultichannelSubstep( trashName={trashName} key={rowKey} volume={row.volume} - ingredNames={ingredNames} source={row.source} dest={row.dest} stepId={stepId} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/PipettingSubsteps.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/PipettingSubsteps.tsx index 5c2be0bc9fa..82da4015e45 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/PipettingSubsteps.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/PipettingSubsteps.tsx @@ -4,7 +4,6 @@ import { MultichannelSubstep } from './MultichannelSubstep' import type { SourceDestSubstepItem, SubstepIdentifier, - WellIngredientNames, } from '../../../../steplist' import { useSelector } from 'react-redux' import { @@ -14,13 +13,12 @@ import { interface PipettingSubstepsProps { substeps: SourceDestSubstepItem - ingredNames: WellIngredientNames selectSubstep: (substepIdentifier: SubstepIdentifier) => void hoveredSubstep?: SubstepIdentifier | null } export function PipettingSubsteps(props: PipettingSubstepsProps): JSX.Element { - const { substeps, selectSubstep, hoveredSubstep, ingredNames } = props + const { substeps, selectSubstep, hoveredSubstep } = props const stepId = substeps.parentStepId const formData = useSelector(getSavedStepForms)[stepId] const additionalEquipment = useSelector(getAdditionalEquipment) @@ -52,7 +50,6 @@ export function PipettingSubsteps(props: PipettingSubstepsProps): JSX.Element { stepId={substeps.parentStepId} substepIndex={groupKey} selectSubstep={selectSubstep} - ingredNames={ingredNames} isSameLabware={isSameLabware} /> ) @@ -64,7 +61,6 @@ export function PipettingSubsteps(props: PipettingSubstepsProps): JSX.Element { selectSubstep={selectSubstep} stepId={substeps.parentStepId} substepIndex={substepIndex} - ingredNames={ingredNames} volume={row.volume} source={row.source} dest={row.dest} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx index 4ed55987f08..9cde39888ce 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx @@ -45,8 +45,8 @@ import type { IconName } from '@opentrons/components' import type { StepIdType } from '../../../../form-types' import type { BaseState } from '../../../../types' -const STARTING_DECK_STATE = 'Starting deck state' -const FINAL_DECK_STATE = 'Final deck state' +const STARTING_DECK_STATE = 'Starting deck' +const FINAL_DECK_STATE = 'Ending deck' const PX_HEIGHT_TO_TOP_OF_CONTAINER = 32 export interface StepContainerProps { title: string diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepOverflowMenu.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepOverflowMenu.tsx index 5bb125b3269..3dd7f529743 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepOverflowMenu.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepOverflowMenu.tsx @@ -64,7 +64,9 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { const isPipetteStep = savedStepFormData.stepType === 'moveLiquid' || savedStepFormData.stepType === 'mix' - const isThermocyclerProfile = savedStepFormData.stepType === 'thermocycler' + const isThermocyclerProfile = + savedStepFormData.stepType === 'thermocycler' && + savedStepFormData.thermocyclerFormType === 'thermocyclerProfile' const duplicateStep = ( stepId: StepIdType diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/Substep.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/Substep.tsx index a34dc799337..7c62f23140d 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/Substep.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/Substep.tsx @@ -1,38 +1,24 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' import noop from 'lodash/noop' -import { AIR } from '@opentrons/step-generation' import { ALIGN_CENTER, - COLORS, DIRECTION_COLUMN, DeckInfoLabel, Flex, JUSTIFY_SPACE_BETWEEN, - LiquidIcon, ListItem, SPACING, StyledText, Tag, } from '@opentrons/components' -import { selectors } from '../../../../labware-ingred/selectors' -import { - MIXED_WELL_COLOR, - swatchColors, -} from '../../../../components/swatchColors' -import { compactPreIngreds, formatVolume } from './utils' +import { formatVolume } from './utils' import type { AdditionalEquipmentName } from '@opentrons/step-generation' -import type { - SubstepIdentifier, - SubstepWellData, - WellIngredientNames, -} from '../../../../steplist' +import type { SubstepIdentifier, SubstepWellData } from '../../../../steplist' interface SubstepProps { trashName: AdditionalEquipmentName | null - ingredNames: WellIngredientNames stepId: string substepIndex: number volume?: number | string | null @@ -45,7 +31,6 @@ interface SubstepProps { function SubstepComponent(props: SubstepProps): JSX.Element { const { volume, - ingredNames, stepId, substepIndex, source, @@ -54,24 +39,14 @@ function SubstepComponent(props: SubstepProps): JSX.Element { selectSubstep: propSelectSubstep, isSameLabware, } = props - const { t } = useTranslation(['application', 'protocol_steps', 'shared']) - const compactedSourcePreIngreds = source - ? compactPreIngreds(source.preIngreds) - : {} + const { i18n, t } = useTranslation([ + 'application', + 'protocol_steps', + 'shared', + ]) const selectSubstep = propSelectSubstep ?? noop - const ingredIds: string[] = Object.keys(compactedSourcePreIngreds) - const liquidDisplayColors = useSelector(selectors.getLiquidDisplayColors) - const noColor = ingredIds.filter(id => id !== AIR).length === 0 - let color = MIXED_WELL_COLOR - if (ingredIds.length === 1) { - color = - liquidDisplayColors[Number(ingredIds[0])] ?? swatchColors(ingredIds[0]) - } else if (noColor) { - color = COLORS.transparent - } - const volumeTag = ( - {ingredIds.length > 0 ? ( - - - - - {ingredIds.map(groupId => ingredNames[groupId]).join(',')} - - - ) : null} - {t('protocol_steps:mix')} @@ -123,33 +88,27 @@ function SubstepComponent(props: SubstepProps): JSX.Element { {t('protocol_steps:in')} ) : ( <> - - - {ingredIds.length > 0 ? ( - - - - - {ingredIds.map(groupId => ingredNames[groupId]).join(',')} - - - ) : null} - {source != null ? ( + {source != null ? ( + + {t('protocol_steps:aspirated')} @@ -159,16 +118,19 @@ function SubstepComponent(props: SubstepProps): JSX.Element { {t('protocol_steps:from')} - ) : null} - - - - {dest !== undefined ? ( + + + ) : null} + {dest != null ? ( + - {ingredIds.length > 0 ? ( - - - - {ingredIds.map(groupId => ingredNames[groupId]).join(',')} - - - ) : null} {dest != null || trashName != null ? ( @@ -195,19 +149,20 @@ function SubstepComponent(props: SubstepProps): JSX.Element { ) : null} - ) : null} - + + ) : null} )} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/SubstepsToolbox.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/SubstepsToolbox.tsx index 3a1ddff44f0..e2460741ebf 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/SubstepsToolbox.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/SubstepsToolbox.tsx @@ -8,7 +8,6 @@ import { StyledText, Toolbox, } from '@opentrons/components' -import { selectors as labwareIngredSelectors } from '../../../../labware-ingred/selectors' import { getSubsteps } from '../../../../file-data/selectors' import { getHoveredSubstep } from '../../../../ui/steps' import { @@ -40,7 +39,6 @@ export function SubstepsToolbox( const substeps = useSelector(getSubsteps)[stepId] const formData = useSelector(getSavedStepForms)[stepId] const hoveredSubstep = useSelector(getHoveredSubstep) - const ingredNames = useSelector(labwareIngredSelectors.getLiquidNamesById) const highlightSubstep = (payload: SubstepIdentifier): HoverOnSubstepAction => dispatch(hoverOnSubstep(payload)) @@ -85,7 +83,6 @@ export function SubstepsToolbox( ) : ( { > { }} /> - + ) diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/utils.ts b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/utils.ts index c7f6f812dc2..56b78507a12 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/utils.ts +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/utils.ts @@ -1,8 +1,6 @@ import round from 'lodash/round' -import omitBy from 'lodash/omitBy' import uniq from 'lodash/uniq' import { UAParser } from 'ua-parser-js' -import type { WellIngredientVolumeData } from '../../../../steplist' import type { StepIdType } from '../../../../form-types' export const capitalizeFirstLetterAfterNumber = (title: string): string => @@ -29,31 +27,6 @@ export const formatPercentage = (part: number, total: number): string => { return `${round((part / total) * 100, PERCENTAGE_DECIMALS_ALLOWED)}%` } -export const compactPreIngreds = ( - preIngreds: WellIngredientVolumeData -): Partial< - | { - [ingredId: string]: - | { - volume: number - } - | undefined - } - | { - [well: string]: - | { - [ingredId: string]: { - volume: number - } - } - | undefined - } -> => { - return omitBy(preIngreds, ingred => { - return typeof ingred?.volume === 'number' && ingred.volume <= 0 - }) -} - export const getMetaSelectedSteps = ( multiSelectItemIds: StepIdType[] | null, stepId: StepIdType, diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/__tests__/ProtocolSteps.test.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/__tests__/ProtocolSteps.test.tsx index f68928c3488..dcfb3dfd58b 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/__tests__/ProtocolSteps.test.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/__tests__/ProtocolSteps.test.tsx @@ -10,6 +10,7 @@ import { import { getSelectedStepId, getSelectedSubstep, + getSelectedTerminalItemId, } from '../../../../ui/steps/selectors' import { getDesignerTab } from '../../../../file-data/selectors' import { getEnableHotKeysDisplay } from '../../../../feature-flags/selectors' @@ -60,6 +61,7 @@ describe('ProtocolSteps', () => { vi.mocked(DeckSetupContainer).mockReturnValue(
mock DeckSetupContainer
) + vi.mocked(getSelectedTerminalItemId).mockReturnValue(null) vi.mocked(OffDeck).mockReturnValue(
mock OffDeck
) vi.mocked(getUnsavedForm).mockReturnValue(null) vi.mocked(getSelectedSubstep).mockReturnValue(null) @@ -95,8 +97,8 @@ describe('ProtocolSteps', () => { it('renders the hot keys display', () => { render() screen.getByText('Double-click to edit') - screen.getByText('Shift + Click to select all') - screen.getByText('Command + Click for multi-select') + screen.getByText('⇧ + click to select range') + screen.getByText('^/⌘ + click to select multiple') }) it('renders the current step name', () => { diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/index.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/index.tsx index b5e5810c2da..8cb7d8fbfe2 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/index.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/index.tsx @@ -3,12 +3,10 @@ import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, - Box, COLORS, DIRECTION_COLUMN, Flex, JUSTIFY_CENTER, - JUSTIFY_FLEX_END, JUSTIFY_FLEX_START, JUSTIFY_SPACE_BETWEEN, POSITION_FIXED, @@ -27,6 +25,8 @@ import { getSelectedSubstep, getSelectedStepId, getHoveredStepId, + getSelectedTerminalItemId, + getHoveredTerminalItemId, } from '../../../ui/steps/selectors' import { DeckSetupContainer } from '../DeckSetup' import { OffDeck } from '../Offdeck' @@ -42,6 +42,8 @@ const CONTENT_MAX_WIDTH = '46.9375rem' export function ProtocolSteps(): JSX.Element { const { i18n, t } = useTranslation('starting_deck_state') const formData = useSelector(getUnsavedForm) + const selectedTerminalItem = useSelector(getSelectedTerminalItemId) + const hoveredTerminalItem = useSelector(getHoveredTerminalItemId) const isMultiSelectMode = useSelector(getIsMultiSelectMode) const selectedSubstep = useSelector(getSelectedSubstep) const enableHoyKeyDisplay = useSelector(getEnableHotKeysDisplay) @@ -89,16 +91,19 @@ export function ProtocolSteps(): JSX.Element { {tab === 'protocolSteps' ? ( ) : null} - - {currentStep != null ? ( + + {currentStep != null && hoveredTerminalItem == null ? ( {i18n.format(currentStep.stepName, 'capitalize')} ) : null} + {(hoveredTerminalItem != null || selectedTerminalItem != null) && + currentHoveredStepId == null ? ( + + {t(hoveredTerminalItem ?? selectedTerminalItem)} + + ) : null} + ) : null} + {selectedTerminalItem != null && currentHoveredStepId == null ? ( + + ) : null} {enableHoyKeyDisplay ? ( - - - - - - - + + + + + ) : null} {formData == null && selectedSubstep ? ( diff --git a/protocol-designer/src/pages/Designer/index.tsx b/protocol-designer/src/pages/Designer/index.tsx index 8993d271420..9b2c1beda35 100644 --- a/protocol-designer/src/pages/Designer/index.tsx +++ b/protocol-designer/src/pages/Designer/index.tsx @@ -161,10 +161,15 @@ export function Designer(): JSX.Element { - + {zoomIn.slot == null ? ( { it('renders the settings page without the dev ffs visible', () => { render() screen.getByText('Settings') - screen.getByText('App settings') + screen.getByText('App Info') screen.getByText('Protocol designer version') screen.getByText('fake_PD_version') screen.getAllByText('Release notes') - screen.getByText('User settings') + screen.getByText('User Settings') screen.getByText('Hints') - screen.getByText('Reset all hints and tips notifications') - screen.getByText('Timeline editing tips') + screen.getByText('Show all hints and tips notifications again') + screen.getByText('Timeline editing guidance') screen.getByText( - 'Show tips for working with steps next to the protocol timeline' + 'Show information about working with steps next to the protocol timeline' ) - screen.getByText('Reset hints') + screen.getByText('Reset') screen.getByText('Privacy') - screen.getByText('Share sessions with Opentrons') + screen.getByText('Share analytics with Opentrons') screen.debug() - screen.getByRole('link', { name: 'privacy policy' }) - screen.getByRole('link', { name: 'EULA' }) - screen.getByRole('link', { name: 'Software manual' }) }) it('renders the announcement modal when view release notes button is clicked', () => { vi.mocked(AnnouncementModal).mockReturnValue( @@ -70,7 +67,7 @@ describe('Settings', () => { }) it('renders the hints button and calls to dismiss them when text is pressed', () => { render() - fireEvent.click(screen.getByText('Reset hints')) + fireEvent.click(screen.getByText('Reset')) expect(vi.mocked(clearAllHintDismissals)).toHaveBeenCalled() }) it('renders the analytics toggle and calls the action when pressed', () => { @@ -85,7 +82,7 @@ describe('Settings', () => { }) render() - screen.getByText('Developer feature flags') + screen.getByText('Developer Feature Flags') screen.getByText('Use prerelease mode') screen.getByText('Show in-progress features for testing & internal use') screen.getByText('Disable module placement restrictions') diff --git a/protocol-designer/src/pages/Settings/index.tsx b/protocol-designer/src/pages/Settings/index.tsx index 32669c3bd60..b678327adb8 100644 --- a/protocol-designer/src/pages/Settings/index.tsx +++ b/protocol-designer/src/pages/Settings/index.tsx @@ -52,6 +52,7 @@ export function Settings(): JSX.Element { : analyticsActions.optIn const prereleaseModeEnabled = flags.PRERELEASE_MODE === true + const pdVersion = process.env.OT_PD_VERSION const allFlags = Object.keys(flags) as FlagTypes[] @@ -126,7 +127,7 @@ export function Settings(): JSX.Element { - {t('shared:app_settings')} + {t('shared:app_info')} - {process.env.OT_PD_VERSION} + {pdVersion} @@ -185,7 +186,7 @@ export function Settings(): JSX.Element {
- {t('shared:reset_hints_and_tips')} + {t('shared:show_hints_and_tips')}
@@ -202,7 +203,7 @@ export function Settings(): JSX.Element { > {canClearHintDismissals - ? t('shared:reset_hints') + ? t('shared:reset') : t('shared:no_hints_to_restore')} @@ -245,7 +246,7 @@ export function Settings(): JSX.Element { > - {t('shared:shared_sessions')} + {t('shared:shared_analytics')} None: """Add a new 'source' column to data_files table.""" - select_data_files = sqlalchemy.select(schema_7.data_files_table).order_by( - sqlite_rowid - ) - insert_new_data = sqlalchemy.insert(schema_7.data_files_table) - for old_row in dest_transaction.execute(select_data_files).all(): - dest_transaction.execute( - insert_new_data, - id=old_row.id, - name=old_row.name, - file_hash=old_row.file_hash, - created_at=old_row.created_at, - source=DataFileSourceSQLEnum.UPLOADED, + dest_transaction.execute( + sqlalchemy.update(schema_7.data_files_table).values( + {"source": DataFileSourceSQLEnum.UPLOADED} ) + ) diff --git a/robot-server/robot_server/protocols/analysis_models.py b/robot-server/robot_server/protocols/analysis_models.py index 1e377aec3dd..61a66866bb0 100644 --- a/robot-server/robot_server/protocols/analysis_models.py +++ b/robot-server/robot_server/protocols/analysis_models.py @@ -19,6 +19,7 @@ LoadedModule, LoadedPipette, Liquid, + LiquidClassRecordWithId, ) @@ -185,6 +186,10 @@ class CompletedAnalysis(BaseModel): default_factory=list, description="Liquids used by the protocol", ) + liquidClasses: List[LiquidClassRecordWithId] = Field( + default_factory=list, + description="Liquid classes used by the protocol", + ) errors: List[ErrorOccurrence] = Field( ..., description=( diff --git a/robot-server/robot_server/protocols/analysis_store.py b/robot-server/robot_server/protocols/analysis_store.py index 71d170c6581..2f46f7857cb 100644 --- a/robot-server/robot_server/protocols/analysis_store.py +++ b/robot-server/robot_server/protocols/analysis_store.py @@ -19,6 +19,7 @@ LoadedLabware, LoadedModule, Liquid, + LiquidClassRecordWithId, ) from opentrons.protocol_engine.protocol_engine import code_in_error_tree @@ -152,6 +153,7 @@ async def update( pipettes: List[LoadedPipette], errors: List[ErrorOccurrence], liquids: List[Liquid], + liquidClasses: List[LiquidClassRecordWithId], ) -> None: """Promote a pending analysis to completed, adding details of its results. @@ -167,6 +169,7 @@ async def update( errors: See `CompletedAnalysis.errors`. Also used to infer whether the completed analysis result is `OK` or `NOT_OK`. liquids: See `CompletedAnalysis.liquids`. + liquidClasses: See `CompletedAnalysis.liquidClasses`. robot_type: See `CompletedAnalysis.robotType`. """ protocol_id = self._pending_store.get_protocol_id(analysis_id=analysis_id) @@ -201,6 +204,7 @@ async def update( pipettes=pipettes, errors=errors, liquids=liquids, + liquidClasses=liquidClasses, ) completed_analysis_resource = CompletedAnalysisResource( id=completed_analysis.id, @@ -241,6 +245,7 @@ async def save_initialization_failed_analysis( pipettes=[], errors=errors, liquids=[], + liquidClasses=[], ) completed_analysis_resource = CompletedAnalysisResource( id=completed_analysis.id, diff --git a/robot-server/robot_server/protocols/protocol_analyzer.py b/robot-server/robot_server/protocols/protocol_analyzer.py index 89387c5cefc..cf1d0687062 100644 --- a/robot-server/robot_server/protocols/protocol_analyzer.py +++ b/robot-server/robot_server/protocols/protocol_analyzer.py @@ -107,6 +107,7 @@ async def analyze( pipettes=result.state_summary.pipettes, errors=result.state_summary.errors, liquids=result.state_summary.liquids, + liquidClasses=result.state_summary.liquidClasses, ) async def update_to_failed_analysis( @@ -136,6 +137,7 @@ async def update_to_failed_analysis( ) ], liquids=[], + liquidClasses=[], ) def __del__(self) -> None: diff --git a/robot-server/robot_server/runs/router/base_router.py b/robot-server/robot_server/runs/router/base_router.py index 5a2c1047be3..23153669d41 100644 --- a/robot-server/robot_server/runs/router/base_router.py +++ b/robot-server/robot_server/runs/router/base_router.py @@ -61,6 +61,7 @@ RunCurrentState, CommandLinkNoMeta, NozzleLayoutConfig, + TipState, ) from ..run_auto_deleter import RunAutoDeleter from ..run_models import Run, BadRun, RunCreate, RunUpdate @@ -591,33 +592,27 @@ async def get_current_state( # noqa: C901 """ try: run = run_data_manager.get(run_id=runId) - active_nozzle_maps = run_data_manager.get_nozzle_maps(run_id=runId) - - nozzle_layouts = { - pipetteId: ActiveNozzleLayout.construct( - startingNozzle=nozzle_map.starting_nozzle, - activeNozzles=nozzle_map.active_nozzles, - config=NozzleLayoutConfig(nozzle_map.configuration.value.lower()), - ) - for pipetteId, nozzle_map in active_nozzle_maps.items() - } - - run = run_data_manager.get(run_id=runId) - current_command = run_data_manager.get_current_command(run_id=runId) - last_completed_command = run_data_manager.get_last_completed_command( - run_id=runId - ) except RunNotCurrentError as e: raise RunStopped(detail=str(e)).as_error(status.HTTP_409_CONFLICT) - links = CurrentStateLinks.construct( - lastCompleted=CommandLinkNoMeta.construct( - id=last_completed_command.command_id, - href=f"/runs/{runId}/commands/{last_completed_command.command_id}", + active_nozzle_maps = run_data_manager.get_nozzle_maps(run_id=runId) + nozzle_layouts = { + pipetteId: ActiveNozzleLayout.construct( + startingNozzle=nozzle_map.starting_nozzle, + activeNozzles=nozzle_map.active_nozzles, + config=NozzleLayoutConfig(nozzle_map.configuration.value.lower()), ) - if last_completed_command is not None - else None - ) + for pipetteId, nozzle_map in active_nozzle_maps.items() + } + + tip_states = { + pipette_id: TipState.construct(hasTip=has_tip) + for pipette_id, has_tip in run_data_manager.get_tip_attached( + run_id=runId + ).items() + } + + current_command = run_data_manager.get_current_command(run_id=runId) estop_engaged = False place_labware = None @@ -672,11 +667,22 @@ async def get_current_state( # noqa: C901 if place_labware: break + last_completed_command = run_data_manager.get_last_completed_command(run_id=runId) + links = CurrentStateLinks.construct( + lastCompleted=CommandLinkNoMeta.construct( + id=last_completed_command.command_id, + href=f"/runs/{runId}/commands/{last_completed_command.command_id}", + ) + if last_completed_command is not None + else None + ) + return await PydanticResponse.create( content=Body.construct( data=RunCurrentState.construct( estopEngaged=estop_engaged, activeNozzleLayouts=nozzle_layouts, + tipStates=tip_states, placeLabwareState=place_labware, ), links=links, diff --git a/robot-server/robot_server/runs/run_data_manager.py b/robot-server/robot_server/runs/run_data_manager.py index 47fe28232d1..cbbcd022eb6 100644 --- a/robot-server/robot_server/runs/run_data_manager.py +++ b/robot-server/robot_server/runs/run_data_manager.py @@ -1,7 +1,7 @@ """Manage current and historical run data.""" from datetime import datetime -from typing import List, Optional, Callable, Union, Mapping +from typing import Dict, List, Optional, Callable, Union, Mapping from opentrons_shared_data.labware.labware_definition import LabwareDefinition from opentrons_shared_data.errors.exceptions import InvalidStoredData, EnumeratedError @@ -66,6 +66,7 @@ def _build_run( completedAt=state_summary.completedAt, startedAt=state_summary.startedAt, liquids=state_summary.liquids, + liquidClasses=state_summary.liquidClasses, outputFileIds=state_summary.files, runTimeParameters=run_time_parameters, ) @@ -80,6 +81,7 @@ def _build_run( pipettes=[], modules=[], liquids=[], + liquidClasses=[], wells=[], files=[], hasEverEnteredErrorRecovery=False, @@ -124,6 +126,7 @@ def _build_run( completedAt=state.completedAt, startedAt=state.startedAt, liquids=state.liquids, + liquidClasses=state.liquidClasses, runTimeParameters=run_time_parameters, outputFileIds=state.files, hasEverEnteredErrorRecovery=state.hasEverEnteredErrorRecovery, @@ -516,6 +519,13 @@ def get_nozzle_maps(self, run_id: str) -> Mapping[str, NozzleMapInterface]: raise RunNotCurrentError() + def get_tip_attached(self, run_id: str) -> Dict[str, bool]: + """Get current tip attached states, keyed by pipette id.""" + if run_id == self._run_orchestrator_store.current_run_id: + return self._run_orchestrator_store.get_tip_attached() + + raise RunNotCurrentError() + def get_all_commands_as_preserialized_list( self, run_id: str, include_fixit_commands: bool ) -> List[str]: diff --git a/robot-server/robot_server/runs/run_models.py b/robot-server/robot_server/runs/run_models.py index 2ed77b0d0bc..4d5da7560c0 100644 --- a/robot-server/robot_server/runs/run_models.py +++ b/robot-server/robot_server/runs/run_models.py @@ -18,6 +18,7 @@ LabwareOffset, LabwareOffsetCreate, Liquid, + LiquidClassRecordWithId, CommandNote, ) from opentrons.protocol_engine.types import ( @@ -134,6 +135,10 @@ class Run(ResourceModel): ..., description="Liquids loaded to the run.", ) + liquidClasses: List[LiquidClassRecordWithId] = Field( + ..., + description="Liquid classes loaded to the run.", + ) labwareOffsets: List[LabwareOffset] = Field( ..., description="Labware offsets to apply as labware are loaded.", @@ -215,6 +220,10 @@ class BadRun(ResourceModel): ..., description="Liquids loaded to the run.", ) + liquidClasses: List[LiquidClassRecordWithId] = Field( + ..., + description="Liquid classes loaded to the run.", + ) labwareOffsets: List[LabwareOffset] = Field( ..., description="Labware offsets to apply as labware are loaded.", @@ -316,6 +325,16 @@ class ActiveNozzleLayout(BaseModel): ) +class TipState(BaseModel): + """Information about the tip, if any, currently attached to a pipette.""" + + hasTip: bool + + # todo(mm, 2024-11-15): I think the frontend is currently scraping the commands + # list to figure out where the current tip came from. Extend this class with that + # information so the frontend doesn't have to do that. + + class PlaceLabwareState(BaseModel): """Details the labware being placed by the gripper.""" @@ -331,9 +350,21 @@ class PlaceLabwareState(BaseModel): class RunCurrentState(BaseModel): """Current details about a run.""" - estopEngaged: bool = Field(..., description="") - activeNozzleLayouts: Dict[str, ActiveNozzleLayout] = Field(...) - placeLabwareState: Optional[PlaceLabwareState] = Field(None) + # todo(mm, 2024-11-15): Having estopEngaged here is a bit of an odd man out because + # it's sensor state that can change on its own at any time, whereas the rest of + # these fields are logical state that changes only when commands are run. + # + # Our current mechanism for anchoring these fields to a specific point in time + # (important for avoiding torn-read problems when a client combines this info with + # info from other endpoints) is `links.currentCommand`, which is based on the idea + # that these fields only change when the current command changes. + # + # We should see if clients can replace this with `GET /robot/control/estopStatus`. + estopEngaged: bool + + activeNozzleLayouts: Dict[str, ActiveNozzleLayout] + tipStates: Dict[str, TipState] + placeLabwareState: Optional[PlaceLabwareState] class CommandLinkNoMeta(BaseModel): diff --git a/robot-server/robot_server/runs/run_orchestrator_store.py b/robot-server/robot_server/runs/run_orchestrator_store.py index 250f5bea966..b4bd757150e 100644 --- a/robot-server/robot_server/runs/run_orchestrator_store.py +++ b/robot-server/robot_server/runs/run_orchestrator_store.py @@ -2,7 +2,7 @@ import asyncio import logging -from typing import List, Optional, Callable, Mapping +from typing import Dict, List, Optional, Callable, Mapping from opentrons.types import NozzleMapInterface from opentrons.protocol_engine.errors.exceptions import EStopActivatedError @@ -296,6 +296,9 @@ async def clear(self) -> RunResult: state_summary=run_data, commands=commands, parameters=run_time_parameters ) + # todo(mm, 2024-11-15): Are all of these pass-through methods helpful? + # Can we delete them and make callers just call .run_orchestrator.play(), etc.? + def play(self, deck_configuration: Optional[DeckConfigurationType] = None) -> None: """Start or resume the run.""" self.run_orchestrator.play(deck_configuration=deck_configuration) @@ -332,6 +335,10 @@ def get_nozzle_maps(self) -> Mapping[str, NozzleMapInterface]: """Get the current nozzle map keyed by pipette id.""" return self.run_orchestrator.get_nozzle_maps() + def get_tip_attached(self) -> Dict[str, bool]: + """Get current tip state keyed by pipette id.""" + return self.run_orchestrator.get_tip_attached() + def get_run_time_parameters(self) -> List[RunTimeParameter]: """Parameter definitions defined by protocol, if any. Will always be empty before execution.""" return self.run_orchestrator.get_run_time_parameters() diff --git a/robot-server/tests/data_files/test_data_files_store.py b/robot-server/tests/data_files/test_data_files_store.py index 581577d0a16..9a9b722e6ec 100644 --- a/robot-server/tests/data_files/test_data_files_store.py +++ b/robot-server/tests/data_files/test_data_files_store.py @@ -99,6 +99,7 @@ def _get_sample_analysis_resource( commands=[], errors=[], liquids=[], + liquidClasses=[], ), ) diff --git a/robot-server/tests/integration/http_api/protocols/test_v6_json_upload.tavern.yaml b/robot-server/tests/integration/http_api/protocols/test_v6_json_upload.tavern.yaml index 717280a6703..516221c500c 100644 --- a/robot-server/tests/integration/http_api/protocols/test_v6_json_upload.tavern.yaml +++ b/robot-server/tests/integration/http_api/protocols/test_v6_json_upload.tavern.yaml @@ -590,6 +590,7 @@ stages: displayName: Water description: Liquid H2O displayColor: '#7332a8' + liquidClasses: [] --- test_name: Upload and analyze a JSONv6 protocol, with liquids diff --git a/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_flex.tavern.yaml b/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_flex.tavern.yaml index 35801f8719a..022c86da35e 100644 --- a/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_flex.tavern.yaml +++ b/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_flex.tavern.yaml @@ -623,6 +623,7 @@ stages: displayName: Water description: Liquid H2O displayColor: '#7332a8' + liquidClasses: [] --- test_name: Upload and analyze a JSONv8 protocol, with liquids diff --git a/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_ot2.tavern.yaml b/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_ot2.tavern.yaml index f85e307e961..961a9a26601 100644 --- a/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_ot2.tavern.yaml +++ b/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_ot2.tavern.yaml @@ -626,6 +626,7 @@ stages: displayName: Water description: Liquid H2O displayColor: '#7332a8' + liquidClasses: [] --- test_name: Upload and analyze a JSONv8 protocol, with liquids diff --git a/robot-server/tests/integration/http_api/runs/test_json_v6_protocol_run.tavern.yaml b/robot-server/tests/integration/http_api/runs/test_json_v6_protocol_run.tavern.yaml index fd98c29a2dc..580688e6e65 100644 --- a/robot-server/tests/integration/http_api/runs/test_json_v6_protocol_run.tavern.yaml +++ b/robot-server/tests/integration/http_api/runs/test_json_v6_protocol_run.tavern.yaml @@ -51,6 +51,7 @@ stages: displayName: Water description: Liquid H2O displayColor: '#7332a8' + liquidClasses: [] runTimeParameters: [] outputFileIds: [] protocolId: '{protocol_id}' diff --git a/robot-server/tests/integration/http_api/runs/test_json_v7_protocol_run.tavern.yaml b/robot-server/tests/integration/http_api/runs/test_json_v7_protocol_run.tavern.yaml index 3ab7386ba4f..1caf41fbfd1 100644 --- a/robot-server/tests/integration/http_api/runs/test_json_v7_protocol_run.tavern.yaml +++ b/robot-server/tests/integration/http_api/runs/test_json_v7_protocol_run.tavern.yaml @@ -53,6 +53,7 @@ stages: displayName: Water description: Liquid H2O displayColor: '#7332a8' + liquidClasses: [] protocolId: '{protocol_id}' - name: Execute a setup command diff --git a/robot-server/tests/integration/http_api/runs/test_protocol_run.tavern.yaml b/robot-server/tests/integration/http_api/runs/test_protocol_run.tavern.yaml index 2ad0a92eb8c..732726d39e9 100644 --- a/robot-server/tests/integration/http_api/runs/test_protocol_run.tavern.yaml +++ b/robot-server/tests/integration/http_api/runs/test_protocol_run.tavern.yaml @@ -47,6 +47,7 @@ stages: outputFileIds: [] protocolId: '{protocol_id}' liquids: [] + liquidClasses: [] save: json: original_run_data: data @@ -240,6 +241,7 @@ stages: createdAt: !re_fullmatch "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d+(Z|([+-]\\d{2}:\\d{2}))" startedAt: !re_fullmatch "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d+(Z|([+-]\\d{2}:\\d{2}))" liquids: [] + liquidClasses: [] runTimeParameters: [] outputFileIds: [] completedAt: !re_fullmatch "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d+(Z|([+-]\\d{2}:\\d{2}))" diff --git a/robot-server/tests/integration/http_api/runs/test_run_queued_protocol_commands.tavern.yaml b/robot-server/tests/integration/http_api/runs/test_run_queued_protocol_commands.tavern.yaml index 14ae502d800..95f5077c30e 100644 --- a/robot-server/tests/integration/http_api/runs/test_run_queued_protocol_commands.tavern.yaml +++ b/robot-server/tests/integration/http_api/runs/test_run_queued_protocol_commands.tavern.yaml @@ -95,6 +95,7 @@ stages: labware: [] labwareOffsets: [] liquids: [] + liquidClasses: [] runTimeParameters: [] outputFileIds: [] modules: [] diff --git a/robot-server/tests/integration/http_api/runs/test_run_with_run_time_parameters.tavern.yaml b/robot-server/tests/integration/http_api/runs/test_run_with_run_time_parameters.tavern.yaml index 1f44f7101c7..505ff2d8831 100644 --- a/robot-server/tests/integration/http_api/runs/test_run_with_run_time_parameters.tavern.yaml +++ b/robot-server/tests/integration/http_api/runs/test_run_with_run_time_parameters.tavern.yaml @@ -118,6 +118,7 @@ stages: name: sample_plates.csv outputFileIds: [] liquids: [] + liquidClasses: [] protocolId: '{protocol_id}' - name: Play the run diff --git a/robot-server/tests/maintenance_runs/router/test_base_router.py b/robot-server/tests/maintenance_runs/router/test_base_router.py index 35fb6da06c1..29a9c81a3b7 100644 --- a/robot-server/tests/maintenance_runs/router/test_base_router.py +++ b/robot-server/tests/maintenance_runs/router/test_base_router.py @@ -75,6 +75,7 @@ async def test_create_run( labwareOffsets=[], status=pe_types.EngineStatus.IDLE, liquids=[], + liquidClasses=[], hasEverEnteredErrorRecovery=False, ) @@ -150,6 +151,7 @@ async def test_get_run_data_from_url( labware=[], labwareOffsets=[], liquids=[], + liquidClasses=[], hasEverEnteredErrorRecovery=False, ) @@ -200,6 +202,7 @@ async def test_get_run() -> None: labware=[], labwareOffsets=[], liquids=[], + liquidClasses=[], hasEverEnteredErrorRecovery=False, ) @@ -226,6 +229,7 @@ async def test_get_current_run( labware=[], labwareOffsets=[], liquids=[], + liquidClasses=[], hasEverEnteredErrorRecovery=False, ) decoy.when(mock_maintenance_run_data_manager.current_run_id).then_return( diff --git a/robot-server/tests/maintenance_runs/router/test_labware_router.py b/robot-server/tests/maintenance_runs/router/test_labware_router.py index d8a8fdab603..4e5ae1152f2 100644 --- a/robot-server/tests/maintenance_runs/router/test_labware_router.py +++ b/robot-server/tests/maintenance_runs/router/test_labware_router.py @@ -38,6 +38,7 @@ def run() -> MaintenanceRun: modules=[], labwareOffsets=[], liquids=[], + liquidClasses=[], hasEverEnteredErrorRecovery=False, ) diff --git a/robot-server/tests/maintenance_runs/test_run_data_manager.py b/robot-server/tests/maintenance_runs/test_run_data_manager.py index a4431f7b463..07bc9c2e476 100644 --- a/robot-server/tests/maintenance_runs/test_run_data_manager.py +++ b/robot-server/tests/maintenance_runs/test_run_data_manager.py @@ -69,6 +69,7 @@ def engine_state_summary() -> StateSummary: pipettes=[LoadedPipette.construct(id="some-pipette-id")], # type: ignore[call-arg] modules=[LoadedModule.construct(id="some-module-id")], # type: ignore[call-arg] liquids=[Liquid(id="some-liquid-id", displayName="liquid", description="desc")], + liquidClasses=[], wells=[], ) @@ -140,6 +141,7 @@ async def test_create( pipettes=engine_state_summary.pipettes, modules=engine_state_summary.modules, liquids=engine_state_summary.liquids, + liquidClasses=engine_state_summary.liquidClasses, ) @@ -193,6 +195,7 @@ async def test_create_with_options( pipettes=engine_state_summary.pipettes, modules=engine_state_summary.modules, liquids=engine_state_summary.liquids, + liquidClasses=engine_state_summary.liquidClasses, ) @@ -262,6 +265,7 @@ async def test_get_current_run( pipettes=engine_state_summary.pipettes, modules=engine_state_summary.modules, liquids=engine_state_summary.liquids, + liquidClasses=engine_state_summary.liquidClasses, ) assert subject.current_run_id == run_id diff --git a/robot-server/tests/protocols/test_analysis_store.py b/robot-server/tests/protocols/test_analysis_store.py index 1200f5aff43..d15e9925a18 100644 --- a/robot-server/tests/protocols/test_analysis_store.py +++ b/robot-server/tests/protocols/test_analysis_store.py @@ -203,6 +203,7 @@ async def test_returned_in_order_added( commands=[], errors=[], liquids=[], + liquidClasses=[], ) subject.add_pending( @@ -266,6 +267,7 @@ async def test_update_adds_details_and_completes_analysis( commands=[], errors=[], liquids=[], + liquidClasses=[], ) result = await subject.get("analysis-id") @@ -283,6 +285,7 @@ async def test_update_adds_details_and_completes_analysis( commands=[], errors=[], liquids=[], + liquidClasses=[], ) assert await subject.get_by_protocol("protocol-id") == [result] assert json.loads(result_as_document) == { @@ -315,6 +318,7 @@ async def test_update_adds_details_and_completes_analysis( "commands": [], "errors": [], "liquids": [], + "liquidClasses": [], "modules": [], } @@ -364,6 +368,7 @@ async def test_update_adds_rtp_values_to_completed_store( commands=[], errors=[], liquids=[], + liquidClasses=[], ), ) @@ -384,6 +389,7 @@ async def test_update_adds_rtp_values_to_completed_store( commands=[], errors=[], liquids=[], + liquidClasses=[], ) decoy.verify( await mock_completed_store.make_room_and_add( @@ -487,6 +493,7 @@ async def test_update_infers_status_from_errors( modules=[], pipettes=[], liquids=[], + liquidClasses=[], ) analysis = (await subject.get_by_protocol("protocol-id"))[0] assert isinstance(analysis, CompletedAnalysis) @@ -528,6 +535,7 @@ async def test_save_initialization_failed_analysis( commands=[], errors=[error_occurence], liquids=[], + liquidClasses=[], ), ) diff --git a/robot-server/tests/protocols/test_completed_analysis_store.py b/robot-server/tests/protocols/test_completed_analysis_store.py index 42c12565c14..a8112cdda16 100644 --- a/robot-server/tests/protocols/test_completed_analysis_store.py +++ b/robot-server/tests/protocols/test_completed_analysis_store.py @@ -209,6 +209,7 @@ async def test_get_by_analysis_id_as_document( "errors": [], "labware": [], "liquids": [], + "liquidClasses": [], "modules": [], "pipettes": [], } diff --git a/robot-server/tests/protocols/test_protocol_analyzer.py b/robot-server/tests/protocols/test_protocol_analyzer.py index 5d3d9da8a13..3fab95879fe 100644 --- a/robot-server/tests/protocols/test_protocol_analyzer.py +++ b/robot-server/tests/protocols/test_protocol_analyzer.py @@ -189,6 +189,7 @@ async def test_analyze( modules=[], labwareOffsets=[], liquids=[], + liquidClasses=[], wells=[], files=[], hasEverEnteredErrorRecovery=False, @@ -211,6 +212,7 @@ async def test_analyze( pipettes=[analysis_pipette], errors=[], liquids=[], + liquidClasses=[], ) ) @@ -294,5 +296,6 @@ async def test_analyze_updates_pending_on_error( pipettes=[], errors=[error_occurrence], liquids=[], + liquidClasses=[], ), ) diff --git a/robot-server/tests/protocols/test_protocol_store.py b/robot-server/tests/protocols/test_protocol_store.py index ca965d471a8..499bf480cf0 100644 --- a/robot-server/tests/protocols/test_protocol_store.py +++ b/robot-server/tests/protocols/test_protocol_store.py @@ -526,6 +526,7 @@ def get_completed_analysis_resource( commands=[], errors=[], liquids=[], + liquidClasses=[], ), ) @@ -566,6 +567,7 @@ async def test_get_referenced_data_files( commands=[], errors=[], liquids=[], + liquidClasses=[], ), ) analysis_resource2 = CompletedAnalysisResource( @@ -582,6 +584,7 @@ async def test_get_referenced_data_files( commands=[], errors=[], liquids=[], + liquidClasses=[], ), ) diff --git a/robot-server/tests/protocols/test_protocols_router.py b/robot-server/tests/protocols/test_protocols_router.py index 637a2ee082f..0ae2c591ebd 100644 --- a/robot-server/tests/protocols/test_protocols_router.py +++ b/robot-server/tests/protocols/test_protocols_router.py @@ -1495,6 +1495,7 @@ async def test_get_protocol_analyses( commands=[], errors=[], liquids=[], + liquidClasses=[], ) decoy.when(protocol_store.has("protocol-id")).then_return(True) diff --git a/robot-server/tests/runs/router/test_base_router.py b/robot-server/tests/runs/router/test_base_router.py index e43027b3bf1..aa27d37e66b 100644 --- a/robot-server/tests/runs/router/test_base_router.py +++ b/robot-server/tests/runs/router/test_base_router.py @@ -1,6 +1,4 @@ """Tests for base /runs routes.""" -from typing import Dict - from opentrons.hardware_control import HardwareControlAPI from opentrons_shared_data.robot.types import RobotTypeEnum import pytest @@ -53,6 +51,7 @@ ActiveNozzleLayout, CommandLinkNoMeta, NozzleLayoutConfig, + TipState, ) from robot_server.runs.run_orchestrator_store import RunConflictError from robot_server.runs.run_data_manager import ( @@ -112,23 +111,6 @@ def labware_offset_create() -> LabwareOffsetCreate: ) -@pytest.fixture -def mock_nozzle_maps() -> Dict[str, NozzleMap]: - """Get mock NozzleMaps.""" - return { - "mock-pipette-id": NozzleMap( - configuration=NozzleConfigurationType.FULL, - columns={"1": ["A1"]}, - rows={"A": ["A1"]}, - map_store={"A1": Point(0, 0, 0)}, - starting_nozzle="A1", - valid_map_key="mock-key", - full_instrument_map_store={}, - full_instrument_rows={}, - ) - } - - async def test_create_run( decoy: Decoy, mock_run_data_manager: RunDataManager, @@ -157,6 +139,7 @@ async def test_create_run( labwareOffsets=[], status=pe_types.EngineStatus.IDLE, liquids=[], + liquidClasses=[], outputFileIds=[], hasEverEnteredErrorRecovery=False, ) @@ -245,6 +228,7 @@ async def test_create_protocol_run( labwareOffsets=[], status=pe_types.EngineStatus.IDLE, liquids=[], + liquidClasses=[], outputFileIds=[], hasEverEnteredErrorRecovery=False, ) @@ -413,6 +397,7 @@ async def test_get_run_data_from_url( labware=[], labwareOffsets=[], liquids=[], + liquidClasses=[], outputFileIds=[], hasEverEnteredErrorRecovery=False, ) @@ -461,6 +446,7 @@ async def test_get_run() -> None: labware=[], labwareOffsets=[], liquids=[], + liquidClasses=[], outputFileIds=[], hasEverEnteredErrorRecovery=False, ) @@ -508,6 +494,7 @@ async def test_get_runs_not_empty( labware=[], labwareOffsets=[], liquids=[], + liquidClasses=[], outputFileIds=[], hasEverEnteredErrorRecovery=False, ) @@ -525,6 +512,7 @@ async def test_get_runs_not_empty( labware=[], labwareOffsets=[], liquids=[], + liquidClasses=[], outputFileIds=[], hasEverEnteredErrorRecovery=False, ) @@ -605,6 +593,7 @@ async def test_update_run_to_not_current( labware=[], labwareOffsets=[], liquids=[], + liquidClasses=[], outputFileIds=[], hasEverEnteredErrorRecovery=False, ) @@ -641,6 +630,7 @@ async def test_update_current_none_noop( labware=[], labwareOffsets=[], liquids=[], + liquidClasses=[], outputFileIds=[], hasEverEnteredErrorRecovery=False, ) @@ -876,7 +866,6 @@ async def test_get_current_state_success( decoy: Decoy, mock_run_data_manager: RunDataManager, mock_hardware_api: HardwareControlAPI, - mock_nozzle_maps: Dict[str, NozzleMap], ) -> None: """It should return different state from the current run. @@ -885,8 +874,23 @@ async def test_get_current_state_success( """ run_id = "test-run-id" + decoy.when(mock_run_data_manager.get_tip_attached(run_id=run_id)).then_return( + {"mock-pipette-id": True} + ) + decoy.when(mock_run_data_manager.get_nozzle_maps(run_id=run_id)).then_return( - mock_nozzle_maps + { + "mock-pipette-id": NozzleMap( + configuration=NozzleConfigurationType.FULL, + columns={"1": ["A1"]}, + rows={"A": ["A1"]}, + map_store={"A1": Point(0, 0, 0)}, + starting_nozzle="A1", + valid_map_key="mock-key", + full_instrument_map_store={}, + full_instrument_rows={}, + ) + } ) command_pointer = CommandPointer( command_id="command-id", @@ -918,6 +922,7 @@ async def test_get_current_state_success( config=NozzleLayoutConfig.FULL, ) }, + tipStates={"mock-pipette-id": TipState(hasTip=True)}, ) assert result.content.links == CurrentStateLinks( lastCompleted=CommandLinkNoMeta( @@ -935,7 +940,7 @@ async def test_get_current_state_run_not_current( """It should raise RunStopped when the run is not current.""" run_id = "non-current-run-id" - decoy.when(mock_run_data_manager.get_nozzle_maps(run_id=run_id)).then_raise( + decoy.when(mock_run_data_manager.get(run_id=run_id)).then_raise( RunNotCurrentError("Run is not current") ) diff --git a/robot-server/tests/runs/router/test_labware_router.py b/robot-server/tests/runs/router/test_labware_router.py index 900eac530f1..1252d983efb 100644 --- a/robot-server/tests/runs/router/test_labware_router.py +++ b/robot-server/tests/runs/router/test_labware_router.py @@ -40,6 +40,7 @@ def run() -> Run: labwareOffsets=[], protocolId=None, liquids=[], + liquidClasses=[], outputFileIds=[], hasEverEnteredErrorRecovery=False, ) diff --git a/robot-server/tests/runs/test_run_data_manager.py b/robot-server/tests/runs/test_run_data_manager.py index 5e4aed1f3e2..d27e1aebaff 100644 --- a/robot-server/tests/runs/test_run_data_manager.py +++ b/robot-server/tests/runs/test_run_data_manager.py @@ -105,6 +105,7 @@ def engine_state_summary() -> StateSummary: pipettes=[LoadedPipette.construct(id="some-pipette-id")], # type: ignore[call-arg] modules=[LoadedModule.construct(id="some-module-id")], # type: ignore[call-arg] liquids=[Liquid(id="some-liquid-id", displayName="liquid", description="desc")], + liquidClasses=[], wells=[], ) @@ -288,6 +289,7 @@ async def test_create( pipettes=engine_state_summary.pipettes, modules=engine_state_summary.modules, liquids=engine_state_summary.liquids, + liquidClasses=engine_state_summary.liquidClasses, runTimeParameters=[bool_parameter, file_parameter], outputFileIds=engine_state_summary.files, ) @@ -395,6 +397,7 @@ async def test_get_current_run( pipettes=engine_state_summary.pipettes, modules=engine_state_summary.modules, liquids=engine_state_summary.liquids, + liquidClasses=engine_state_summary.liquidClasses, runTimeParameters=run_time_parameters, outputFileIds=engine_state_summary.files, ) @@ -438,6 +441,7 @@ async def test_get_historical_run( pipettes=engine_state_summary.pipettes, modules=engine_state_summary.modules, liquids=engine_state_summary.liquids, + liquidClasses=engine_state_summary.liquidClasses, runTimeParameters=run_time_parameters, outputFileIds=engine_state_summary.files, ) @@ -482,6 +486,7 @@ async def test_get_historical_run_no_data( pipettes=[], modules=[], liquids=[], + liquidClasses=[], runTimeParameters=run_time_parameters, outputFileIds=[], ) @@ -503,6 +508,7 @@ async def test_get_all_runs( pipettes=[LoadedPipette.construct(id="current-pipette-id")], # type: ignore[call-arg] modules=[LoadedModule.construct(id="current-module-id")], # type: ignore[call-arg] liquids=[Liquid(id="some-liquid-id", displayName="liquid", description="desc")], + liquidClasses=[], wells=[], ) current_run_time_parameters: List[pe_types.RunTimeParameter] = [ @@ -523,6 +529,7 @@ async def test_get_all_runs( pipettes=[LoadedPipette.construct(id="old-pipette-id")], # type: ignore[call-arg] modules=[LoadedModule.construct(id="old-module-id")], # type: ignore[call-arg] liquids=[], + liquidClasses=[], wells=[], ) historical_run_time_parameters: List[pe_types.RunTimeParameter] = [ @@ -584,6 +591,7 @@ async def test_get_all_runs( pipettes=historical_run_data.pipettes, modules=historical_run_data.modules, liquids=historical_run_data.liquids, + liquidClasses=historical_run_data.liquidClasses, runTimeParameters=historical_run_time_parameters, outputFileIds=historical_run_data.files, ), @@ -601,6 +609,7 @@ async def test_get_all_runs( pipettes=current_run_data.pipettes, modules=current_run_data.modules, liquids=current_run_data.liquids, + liquidClasses=current_run_data.liquidClasses, runTimeParameters=current_run_time_parameters, outputFileIds=current_run_data.files, ), @@ -700,6 +709,7 @@ async def test_update_current( pipettes=engine_state_summary.pipettes, modules=engine_state_summary.modules, liquids=engine_state_summary.liquids, + liquidClasses=engine_state_summary.liquidClasses, runTimeParameters=run_time_parameters, outputFileIds=engine_state_summary.files, ) @@ -757,6 +767,7 @@ async def test_update_current_noop( pipettes=engine_state_summary.pipettes, modules=engine_state_summary.modules, liquids=engine_state_summary.liquids, + liquidClasses=engine_state_summary.liquidClasses, runTimeParameters=run_time_parameters, outputFileIds=engine_state_summary.files, ) diff --git a/shared-data/command/schemas/10.json b/shared-data/command/schemas/10.json index ce2e5c82da5..aced561bdff 100644 --- a/shared-data/command/schemas/10.json +++ b/shared-data/command/schemas/10.json @@ -4692,7 +4692,7 @@ "type": "object", "properties": { "axes": { - "description": "The axes for which to update the position estimators.", + "description": "The axes for which to update the position estimators. Any axes that are not physically present will be ignored.", "type": "array", "items": { "$ref": "#/definitions/MotorAxis" diff --git a/shared-data/command/schemas/11.json b/shared-data/command/schemas/11.json index cc2202f850d..432e8a08231 100644 --- a/shared-data/command/schemas/11.json +++ b/shared-data/command/schemas/11.json @@ -1940,16 +1940,35 @@ "airGapByVolume": { "title": "Airgapbyvolume", "description": "Settings for air gap keyed by target aspiration volume.", - "type": "object", - "additionalProperties": { - "anyOf": [ + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ { - "type": "integer", - "minimum": 0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] }, { - "type": "number", - "minimum": 0.0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] } ] } @@ -2073,16 +2092,35 @@ "flowRateByVolume": { "title": "Flowratebyvolume", "description": "Settings for flow rate keyed by target aspiration volume.", - "type": "object", - "additionalProperties": { - "anyOf": [ + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ { - "type": "integer", - "minimum": 0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] }, { - "type": "number", - "minimum": 0.0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] } ] } @@ -2218,16 +2256,35 @@ "airGapByVolume": { "title": "Airgapbyvolume", "description": "Settings for air gap keyed by target aspiration volume.", - "type": "object", - "additionalProperties": { - "anyOf": [ + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ { - "type": "integer", - "minimum": 0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] }, { - "type": "number", - "minimum": 0.0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] } ] } @@ -2313,16 +2370,35 @@ "flowRateByVolume": { "title": "Flowratebyvolume", "description": "Settings for flow rate keyed by target dispense volume.", - "type": "object", - "additionalProperties": { - "anyOf": [ + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ { - "type": "integer", - "minimum": 0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] }, { - "type": "number", - "minimum": 0.0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] } ] } @@ -2339,16 +2415,35 @@ "pushOutByVolume": { "title": "Pushoutbyvolume", "description": "Settings for pushout keyed by target dispense volume.", - "type": "object", - "additionalProperties": { - "anyOf": [ + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ { - "type": "integer", - "minimum": 0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] }, { - "type": "number", - "minimum": 0.0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] } ] } @@ -2417,16 +2512,35 @@ "flowRateByVolume": { "title": "Flowratebyvolume", "description": "Settings for flow rate keyed by target dispense volume.", - "type": "object", - "additionalProperties": { - "anyOf": [ + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ { - "type": "integer", - "minimum": 0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] }, { - "type": "number", - "minimum": 0.0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] } ] } @@ -2434,16 +2548,35 @@ "conditioningByVolume": { "title": "Conditioningbyvolume", "description": "Settings for conditioning volume keyed by target dispense volume.", - "type": "object", - "additionalProperties": { - "anyOf": [ + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ { - "type": "integer", - "minimum": 0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] }, { - "type": "number", - "minimum": 0.0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] } ] } @@ -2451,16 +2584,35 @@ "disposalByVolume": { "title": "Disposalbyvolume", "description": "Settings for disposal volume keyed by target dispense volume.", - "type": "object", - "additionalProperties": { - "anyOf": [ + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ { - "type": "integer", - "minimum": 0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] }, { - "type": "number", - "minimum": 0.0 + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "number", + "minimum": 0.0 + } + ] } ] } @@ -5609,7 +5761,7 @@ "type": "object", "properties": { "axes": { - "description": "The axes for which to update the position estimators.", + "description": "The axes for which to update the position estimators. Any axes that are not physically present will be ignored.", "type": "array", "items": { "$ref": "#/definitions/MotorAxis" diff --git a/shared-data/js/__tests__/liquidClassSchema.test.ts b/shared-data/js/__tests__/liquidClassSchema.test.ts new file mode 100644 index 00000000000..75e477637c9 --- /dev/null +++ b/shared-data/js/__tests__/liquidClassSchema.test.ts @@ -0,0 +1,66 @@ +/** Ensure that the liquid class schema itself functions as intended, + * and that all v1 liquid class fixtures will validate */ +import Ajv from 'ajv' +import path from 'path' +import glob from 'glob' +import { describe, expect, it } from 'vitest' +import liquidClassSchemaV1 from '../../liquid-class/schemas/1.json' + +const fixtureV1Glob = path.join( + __dirname, + '../../liquid-class/fixtures/1/*.json' +) +const defV1Glob = path.join( + __dirname, + '../../liquid-class/definitions/1/*.json' +) + +const ajv = new Ajv({ allErrors: true, jsonPointers: true }) + +const validateSchemaV1 = ajv.compile(liquidClassSchemaV1) + +describe('validate v1 liquid class definitions and fixtures', () => { + const fixtures = glob.sync(fixtureV1Glob) + + fixtures.forEach(fixturePath => { + const fixtureDef = require(fixturePath) + + it('fixture validates against schema', () => { + const valid = validateSchemaV1(fixtureDef) + const validationErrors = validateSchemaV1.errors + + if (validationErrors) { + console.log( + path.parse(fixturePath).base + + ' ' + + JSON.stringify(validationErrors, null, 4) + ) + } + + expect(validationErrors).toBe(null) + expect(valid).toBe(true) + }) + }) + + const defs = glob.sync(defV1Glob) + + defs.forEach(defPath => { + const liquidClassDef = require(defPath) + + it('liquid class definition validates against v1 schema', () => { + const valid = validateSchemaV1(liquidClassDef) + const validationErrors = validateSchemaV1.errors + + if (validationErrors) { + console.log( + path.parse(defPath).base + + ' ' + + JSON.stringify(validationErrors, null, 4) + ) + } + + expect(validationErrors).toBe(null) + expect(valid).toBe(true) + }) + }) +}) diff --git a/shared-data/js/constants.ts b/shared-data/js/constants.ts index 888d9f0c2f7..608bb982887 100644 --- a/shared-data/js/constants.ts +++ b/shared-data/js/constants.ts @@ -515,6 +515,7 @@ export const SINGLE_RIGHT_SLOT_FIXTURE: 'singleRightSlot' = 'singleRightSlot' export const STAGING_AREA_RIGHT_SLOT_FIXTURE: 'stagingAreaRightSlot' = 'stagingAreaRightSlot' +export const TRASH_BIN_FIXTURE: 'trashBin' = 'trashBin' export const TRASH_BIN_ADAPTER_FIXTURE: 'trashBinAdapter' = 'trashBinAdapter' export const WASTE_CHUTE_RIGHT_ADAPTER_COVERED_FIXTURE: 'wasteChuteRightAdapterCovered' = @@ -591,12 +592,6 @@ export const WASTE_CHUTE_STAGING_AREA_FIXTURES: CutoutFixtureId[] = [ export const LOW_VOLUME_PIPETTES = ['p50_single_flex', 'p50_multi_flex'] -// robot server loads absorbance reader lid as a labware but it is not -// user addressable so we need to hide it where we show labware in the app -export const NON_USER_ADDRESSABLE_LABWARE = [ - 'opentrons_flex_lid_absorbance_plate_reader_module', -] - // default hex values for liquid colors const electricPurple = '#b925ff' const goldenYellow = '#ffd600' diff --git a/shared-data/labware/definitions/2/opentrons_flex_deck_riser/1.json b/shared-data/labware/definitions/2/opentrons_flex_deck_riser/1.json index 59f0548ca32..94533e059b2 100644 --- a/shared-data/labware/definitions/2/opentrons_flex_deck_riser/1.json +++ b/shared-data/labware/definitions/2/opentrons_flex_deck_riser/1.json @@ -13,7 +13,7 @@ "dimensions": { "xDimension": 140, "yDimension": 98, - "zDimension": 21 + "zDimension": 55 }, "wells": {}, "groups": [ diff --git a/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json b/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json index 9ae49fd8a5e..e86f24c6015 100644 --- a/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json +++ b/shared-data/labware/definitions/2/opentrons_tough_pcr_auto_sealing_lid/1.json @@ -74,7 +74,7 @@ "opentrons_flex_deck_riser": { "x": 0, "y": 0, - "z": 0 + "z": 34 } }, "gripForce": 15, diff --git a/shared-data/liquid-class/definitions/1/water.json b/shared-data/liquid-class/definitions/1/water.json index b5fc2f75486..b84e1676d5b 100644 --- a/shared-data/liquid-class/definitions/1/water.json +++ b/shared-data/liquid-class/definitions/1/water.json @@ -33,12 +33,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 0.1, - "1": 0.1, - "49.9": 0.1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 0.1], + [49.9, 0.1], + [50.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -60,12 +59,11 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 50, - "1": 35, - "10": 24, - "50": 35 - }, + "flowRateByVolume": [ + [1.0, 35.0], + [10.0, 24.0], + [50.0, 35.0] + ], "preWet": false, "mix": { "enable": false, @@ -105,12 +103,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 0.1, - "1": 0.1, - "49.9": 0.1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 0.1], + [49.9, 0.1], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -135,9 +132,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 50 - }, + "flowRateByVolume": [[1.0, 50.0]], "mix": { "enable": false, "params": { @@ -145,14 +140,13 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 2, - "1": 7, - "4.999": 7, - "5": 2, - "10": 2, - "50": 2 - }, + "pushOutByVolume": [ + [1.0, 7.0], + [4.999, 7.0], + [5.0, 2.0], + [10.0, 2.0], + [50.0, 2.0] + ], "delay": { "enable": true, "params": { @@ -184,12 +178,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 0.1, - "1": 0.1, - "49.9": 0.1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 0.1], + [49.9, 0.1], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -214,21 +207,17 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 50 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, + "flowRateByVolume": [[50.0, 50.0]], + "conditioningByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], "delay": { "enable": true, "params": { @@ -268,12 +257,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 0.1, - "1": 0.1, - "49.9": 0.1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 0.1], + [49.9, 0.1], + [50.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -295,12 +283,11 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 50, - "1": 35, - "10": 24, - "50": 35 - }, + "flowRateByVolume": [ + [1.0, 35.0], + [10.0, 24.0], + [50.0, 35.0] + ], "preWet": false, "mix": { "enable": false, @@ -340,12 +327,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 0.1, - "1": 0.1, - "49.9": 0.1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 0.1], + [49.9, 0.1], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -370,9 +356,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 50 - }, + "flowRateByVolume": [[1.0, 50.0]], "mix": { "enable": false, "params": { @@ -380,14 +364,13 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 2, - "1": 7, - "4.999": 7, - "5": 2, - "10": 2, - "50": 2 - }, + "pushOutByVolume": [ + [1.0, 7.0], + [4.999, 7.0], + [5.0, 2.0], + [10.0, 2.0], + [50.0, 2.0] + ], "delay": { "enable": true, "params": { @@ -419,12 +402,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 0.1, - "1": 0.1, - "49.9": 0.1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 0.1], + [49.9, 0.1], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -449,21 +431,17 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 50 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, + "flowRateByVolume": [[1.0, 50.0]], + "conditioningByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], "delay": { "enable": true, "params": { @@ -503,12 +481,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 1, - "1": 1, - "49": 1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 1.0], + [49.0, 1.0], + [50.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -530,12 +507,11 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 478, - "5": 318, - "10": 478, - "50": 478 - }, + "flowRateByVolume": [ + [5.0, 318.0], + [10.0, 478.0], + [50.0, 478.0] + ], "preWet": false, "mix": { "enable": false, @@ -575,12 +551,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 1, - "1": 1, - "49": 1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 1.0], + [49.0, 1.0], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -605,12 +580,11 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 478, - "5": 318, - "10": 478, - "50": 478 - }, + "flowRateByVolume": [ + [5.0, 318.0], + [10.0, 478.0], + [50.0, 478.0] + ], "mix": { "enable": false, "params": { @@ -618,9 +592,7 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 20 - }, + "pushOutByVolume": [[1.0, 20.0]], "delay": { "enable": false, "params": { @@ -652,12 +624,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 1, - "1": 1, - "49": 1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 1.0], + [49.0, 1.0], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -682,24 +653,21 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 478, - "5": 318, - "10": 478, - "50": 478 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, + "flowRateByVolume": [ + [5.0, 318.0], + [10.0, 478.0], + [50.0, 478.0] + ], + "conditioningByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], "delay": { "enable": false, "params": { @@ -734,12 +702,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "airGapByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -761,9 +728,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, + "flowRateByVolume": [[1.0, 716.0]], "preWet": false, "mix": { "enable": false, @@ -803,12 +768,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "airGapByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "blowout": { "enable": false }, @@ -833,9 +797,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, + "flowRateByVolume": [[1.0, 716.0]], "mix": { "enable": false, "params": { @@ -843,9 +805,7 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 15 - }, + "pushOutByVolume": [[1.0, 15.0]], "delay": { "enable": false, "params": { @@ -877,12 +837,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "airGapByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "blowout": { "enable": false }, @@ -907,21 +866,17 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "flowRateByVolume": [[1.0, 716.0]], + "conditioningByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "delay": { "enable": false, "params": { @@ -956,12 +911,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 10, - "10": 10, - "990": 10, - "1000": 0 - }, + "airGapByVolume": [ + [10.0, 10.0], + [990.0, 10.0], + [1000.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -983,9 +937,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, + "flowRateByVolume": [[1.0, 716.0]], "preWet": false, "mix": { "enable": false, @@ -1025,12 +977,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 10, - "10": 10, - "990": 10, - "1000": 0 - }, + "airGapByVolume": [ + [10.0, 10.0], + [990.0, 10.0], + [1000.0, 0.0] + ], "blowout": { "enable": false }, @@ -1055,9 +1006,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, + "flowRateByVolume": [[1.0, 716.0]], "mix": { "enable": false, "params": { @@ -1065,9 +1014,7 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 20 - }, + "pushOutByVolume": [[1.0, 20.0]], "delay": { "enable": false, "params": { @@ -1099,12 +1046,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 10, - "10": 10, - "990": 10, - "1000": 0 - }, + "airGapByVolume": [ + [10.0, 10.0], + [990.0, 10.0], + [1000.0, 0.0] + ], "blowout": { "enable": false }, @@ -1129,21 +1075,17 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "995": 5, - "1000": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "995": 5, - "1000": 0 - }, + "flowRateByVolume": [[1.0, 716.0]], + "conditioningByVolume": [ + [1.0, 5.0], + [995.0, 5.0], + [1000.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [995.0, 5.0], + [1000.0, 0.0] + ], "delay": { "enable": false, "params": { @@ -1183,12 +1125,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 1, - "1": 1, - "49": 1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 1.0], + [49.0, 1.0], + [50.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -1210,12 +1151,11 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 478, - "5": 318, - "10": 478, - "50": 478 - }, + "flowRateByVolume": [ + [5.0, 318.0], + [10.0, 478.0], + [50.0, 478.0] + ], "preWet": false, "mix": { "enable": false, @@ -1255,12 +1195,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 1, - "1": 1, - "49": 1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 1.0], + [49.0, 1.0], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -1285,12 +1224,11 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 478, - "5": 318, - "10": 478, - "50": 478 - }, + "flowRateByVolume": [ + [5.0, 318.0], + [10.0, 478.0], + [50.0, 478.0] + ], "mix": { "enable": false, "params": { @@ -1298,9 +1236,7 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 20 - }, + "pushOutByVolume": [[1.0, 20.0]], "delay": { "enable": false, "params": { @@ -1332,12 +1268,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 1, - "1": 1, - "49": 1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 1.0], + [49.0, 1.0], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -1362,24 +1297,21 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 478, - "5": 318, - "10": 478, - "50": 478 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, + "flowRateByVolume": [ + [5.0, 318.0], + [10.0, 478.0], + [50.0, 478.0] + ], + "conditioningByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], "delay": { "enable": false, "params": { @@ -1414,12 +1346,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "airGapByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -1441,9 +1372,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, + "flowRateByVolume": [[1.0, 716.0]], "preWet": false, "mix": { "enable": false, @@ -1483,12 +1412,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "airGapByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "blowout": { "enable": false }, @@ -1513,9 +1441,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, + "flowRateByVolume": [[1.0, 716.0]], "mix": { "enable": false, "params": { @@ -1523,9 +1449,7 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 15 - }, + "pushOutByVolume": [[1.0, 15.0]], "delay": { "enable": false, "params": { @@ -1557,12 +1481,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "airGapByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "blowout": { "enable": false }, @@ -1587,21 +1510,17 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "flowRateByVolume": [[1.0, 716.0]], + "conditioningByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "delay": { "enable": false, "params": { @@ -1636,12 +1555,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 10, - "10": 10, - "990": 10, - "1000": 0 - }, + "airGapByVolume": [ + [10.0, 10.0], + [990.0, 10.0], + [1000.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -1663,9 +1581,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, + "flowRateByVolume": [[1.0, 716.0]], "preWet": false, "mix": { "enable": false, @@ -1705,12 +1621,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 10, - "10": 10, - "990": 10, - "1000": 0 - }, + "airGapByVolume": [ + [10.0, 10.0], + [990.0, 10.0], + [1000.0, 0.0] + ], "blowout": { "enable": false }, @@ -1735,9 +1650,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, + "flowRateByVolume": [[1.0, 716.0]], "mix": { "enable": false, "params": { @@ -1745,9 +1658,7 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 20 - }, + "pushOutByVolume": [[1.0, 20.0]], "delay": { "enable": false, "params": { @@ -1779,12 +1690,11 @@ "z": 2 }, "speed": 50, - "airGapByVolume": { - "default": 10, - "10": 10, - "990": 10, - "1000": 0 - }, + "airGapByVolume": [ + [10.0, 10.0], + [990.0, 10.0], + [1000.0, 0.0] + ], "blowout": { "enable": false }, @@ -1809,21 +1719,17 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 716 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "995": 5, - "1000": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "995": 5, - "1000": 0 - }, + "flowRateByVolume": [[1.0, 716.0]], + "conditioningByVolume": [ + [1.0, 5.0], + [995.0, 5.0], + [1000.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [995.0, 5.0], + [1000.0, 0.0] + ], "delay": { "enable": false, "params": { @@ -1863,12 +1769,11 @@ "z": 2 }, "speed": 35, - "airGapByVolume": { - "default": 1, - "1": 1, - "49": 1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 1.0], + [49.0, 1.0], + [50.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -1890,9 +1795,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 200 - }, + "flowRateByVolume": [[1.0, 200.0]], "preWet": false, "mix": { "enable": false, @@ -1932,12 +1835,11 @@ "z": 2 }, "speed": 35, - "airGapByVolume": { - "default": 1, - "1": 1, - "49": 1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 1.0], + [49.0, 1.0], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -1962,9 +1864,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 200 - }, + "flowRateByVolume": [[1.0, 200.0]], "mix": { "enable": false, "params": { @@ -1972,9 +1872,7 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 20 - }, + "pushOutByVolume": [[1.0, 20.0]], "delay": { "enable": false, "params": { @@ -2006,12 +1904,11 @@ "z": 2 }, "speed": 35, - "airGapByVolume": { - "default": 1, - "1": 1, - "49": 1, - "50": 0 - }, + "airGapByVolume": [ + [1.0, 1.0], + [49.0, 1.0], + [50.0, 0.0] + ], "blowout": { "enable": false }, @@ -2036,21 +1933,17 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 200 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "45": 5, - "50": 0 - }, + "flowRateByVolume": [[1.0, 200.0]], + "conditioningByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [45.0, 5.0], + [50.0, 0.0] + ], "delay": { "enable": false, "params": { @@ -2085,12 +1978,11 @@ "z": 2 }, "speed": 35, - "airGapByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "airGapByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -2112,9 +2004,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 200 - }, + "flowRateByVolume": [[1.0, 200.0]], "preWet": false, "mix": { "enable": false, @@ -2154,12 +2044,11 @@ "z": 2 }, "speed": 35, - "airGapByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "airGapByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "blowout": { "enable": false }, @@ -2184,9 +2073,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 200 - }, + "flowRateByVolume": [[1.0, 200.0]], "mix": { "enable": false, "params": { @@ -2194,9 +2081,7 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 15 - }, + "pushOutByVolume": [[1.0, 15.0]], "delay": { "enable": false, "params": { @@ -2228,12 +2113,11 @@ "z": 2 }, "speed": 35, - "airGapByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "airGapByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "blowout": { "enable": false }, @@ -2258,21 +2142,17 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 200 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "195": 5, - "200": 0 - }, + "flowRateByVolume": [[1.0, 200.0]], + "conditioningByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [195.0, 5.0], + [200.0, 0.0] + ], "delay": { "enable": false, "params": { @@ -2307,12 +2187,11 @@ "z": 2 }, "speed": 35, - "airGapByVolume": { - "default": 10, - "10": 10, - "990": 10, - "1000": 0 - }, + "airGapByVolume": [ + [10.0, 10.0], + [990.0, 10.0], + [1000.0, 0.0] + ], "touchTip": { "enable": false, "params": { @@ -2334,9 +2213,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 200 - }, + "flowRateByVolume": [[1.0, 200.0]], "preWet": false, "mix": { "enable": false, @@ -2376,12 +2253,11 @@ "z": 2 }, "speed": 35, - "airGapByVolume": { - "default": 10, - "10": 10, - "990": 10, - "1000": 0 - }, + "airGapByVolume": [ + [10.0, 10.0], + [990.0, 10.0], + [1000.0, 0.0] + ], "blowout": { "enable": false }, @@ -2406,9 +2282,7 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 200 - }, + "flowRateByVolume": [[1.0, 200.0]], "mix": { "enable": false, "params": { @@ -2416,9 +2290,7 @@ "volume": 50 } }, - "pushOutByVolume": { - "default": 20 - }, + "pushOutByVolume": [[1.0, 20.0]], "delay": { "enable": false, "params": { @@ -2450,12 +2322,11 @@ "z": 2 }, "speed": 35, - "airGapByVolume": { - "default": 10, - "10": 10, - "990": 10, - "1000": 0 - }, + "airGapByVolume": [ + [10.0, 10.0], + [990.0, 10.0], + [1000.0, 0.0] + ], "blowout": { "enable": false }, @@ -2480,21 +2351,17 @@ "y": 0, "z": 2 }, - "flowRateByVolume": { - "default": 200 - }, - "conditioningByVolume": { - "default": 5, - "1": 5, - "995": 5, - "1000": 0 - }, - "disposalByVolume": { - "default": 5, - "1": 5, - "995": 5, - "1000": 0 - }, + "flowRateByVolume": [[1.0, 200.0]], + "conditioningByVolume": [ + [1.0, 5.0], + [995.0, 5.0], + [1000.0, 0.0] + ], + "disposalByVolume": [ + [1.0, 5.0], + [995.0, 5.0], + [1000.0, 0.0] + ], "delay": { "enable": false, "params": { diff --git a/shared-data/liquid-class/fixtures/fixture_glycerol50.json b/shared-data/liquid-class/fixtures/1/fixture_glycerol50.json similarity index 82% rename from shared-data/liquid-class/fixtures/fixture_glycerol50.json rename to shared-data/liquid-class/fixtures/1/fixture_glycerol50.json index 8befe1d6a5b..20fe7b44a3c 100644 --- a/shared-data/liquid-class/fixtures/fixture_glycerol50.json +++ b/shared-data/liquid-class/fixtures/1/fixture_glycerol50.json @@ -33,11 +33,10 @@ "z": 5 }, "speed": 100, - "airGapByVolume": { - "default": 2, - "5": 3, - "10": 4 - }, + "airGapByVolume": [ + [5.0, 3.0], + [10.0, 4.0] + ], "touchTip": { "enable": true, "params": { @@ -59,9 +58,7 @@ "y": 0, "z": -5 }, - "flowRateByVolume": { - "default": 50 - }, + "flowRateByVolume": [[10.0, 50.0]], "preWet": true, "mix": { "enable": true, @@ -101,11 +98,10 @@ "z": 5 }, "speed": 100, - "airGapByVolume": { - "default": 2, - "5": 3, - "10": 4 - }, + "airGapByVolume": [ + [5.0, 3.0], + [10.0, 4.0] + ], "blowout": { "enable": true, "params": { @@ -134,11 +130,10 @@ "y": 0, "z": -5 }, - "flowRateByVolume": { - "default": 50, - "10": 40, - "20": 30 - }, + "flowRateByVolume": [ + [10.0, 40.0], + [20.0, 30.0] + ], "mix": { "enable": true, "params": { @@ -146,11 +141,10 @@ "volume": 15 } }, - "pushOutByVolume": { - "default": 5, - "10": 7, - "20": 10 - }, + "pushOutByVolume": [ + [10.0, 7.0], + [20.0, 10.0] + ], "delay": { "enable": true, "params": { @@ -182,11 +176,10 @@ "z": 5 }, "speed": 100, - "airGapByVolume": { - "default": 2, - "5": 3, - "10": 4 - }, + "airGapByVolume": [ + [5.0, 3.0], + [10.0, 4.0] + ], "touchTip": { "enable": true, "params": { @@ -211,19 +204,12 @@ "y": 0, "z": -5 }, - "flowRateByVolume": { - "default": 50, - "10": 40, - "20": 30 - }, - "conditioningByVolume": { - "default": 10, - "5": 5 - }, - "disposalByVolume": { - "default": 2, - "5": 3 - }, + "flowRateByVolume": [ + [10.0, 40.0], + [20.0, 30.0] + ], + "conditioningByVolume": [[5.0, 5.0]], + "disposalByVolume": [[5.0, 3.0]], "delay": { "enable": true, "params": { diff --git a/shared-data/liquid-class/schemas/1.json b/shared-data/liquid-class/schemas/1.json index 1a5eb18d51a..f3aa85a6168 100644 --- a/shared-data/liquid-class/schemas/1.json +++ b/shared-data/liquid-class/schemas/1.json @@ -90,59 +90,59 @@ "additionalProperties": false }, "airGapByVolume": { - "type": "object", + "type": "array", "description": "Settings for air gap keyed by target aspiration volume.", - "properties": { - "default": { "$ref": "#/definitions/positiveNumber" } - }, - "patternProperties": { - "d+": { "$ref": "#/definitions/positiveNumber" } + "items": { + "type": "array", + "items": { "$ref": "#/definitions/positiveNumber" }, + "minItems": 2, + "maxItems": 2 }, - "required": ["default"] + "minItems": 1 }, "flowRateByVolume": { - "type": "object", + "type": "array", "description": "Settings for flow rate keyed by target aspiration/dispense volume.", - "properties": { - "default": { "$ref": "#/definitions/positiveNumber" } - }, - "patternProperties": { - "d+": { "$ref": "#/definitions/positiveNumber" } + "items": { + "type": "array", + "items": { "$ref": "#/definitions/positiveNumber" }, + "minItems": 2, + "maxItems": 2 }, - "required": ["default"] + "minItems": 1 }, "pushOutByVolume": { - "type": "object", + "type": "array", "description": "Settings for pushout keyed by target aspiration volume.", - "properties": { - "default": { "$ref": "#/definitions/positiveNumber" } - }, - "patternProperties": { - "d+": { "$ref": "#/definitions/positiveNumber" } + "items": { + "type": "array", + "items": { "$ref": "#/definitions/positiveNumber" }, + "minItems": 2, + "maxItems": 2 }, - "required": ["default"] + "minItems": 1 }, "disposalByVolume": { - "type": "object", - "description": "Settings for disposal volume keyed by target dispense volume.", - "properties": { - "default": { "$ref": "#/definitions/positiveNumber" } - }, - "patternProperties": { - "d+": { "$ref": "#/definitions/positiveNumber" } + "type": "array", + "description": "An array of two tuples containing positive numbers.", + "items": { + "type": "array", + "items": { "$ref": "#/definitions/positiveNumber" }, + "minItems": 2, + "maxItems": 2 }, - "required": ["default"] + "minItems": 1 }, "conditioningByVolume": { - "type": "object", + "type": "array", "description": "Settings for conditioning volume keyed by target dispense volume.", - "properties": { - "default": { "$ref": "#/definitions/positiveNumber" } - }, - "patternProperties": { - "d+": { "$ref": "#/definitions/positiveNumber" } + "items": { + "type": "array", + "items": { "$ref": "#/definitions/positiveNumber" }, + "minItems": 2, + "maxItems": 2 }, - "required": ["default"] + "minItems": 1 }, "mix": { "type": "object", @@ -409,7 +409,6 @@ "positionReference", "offset", "flowRateByVolume", - "mix", "conditioningByVolume", "disposalByVolume", "delay" diff --git a/shared-data/pipette/definitions/2/general/eight_channel_em/p1000/3_0.json b/shared-data/pipette/definitions/2/general/eight_channel_em/p1000/3_0.json index c49ae20d87a..c267504b404 100644 --- a/shared-data/pipette/definitions/2/general/eight_channel_em/p1000/3_0.json +++ b/shared-data/pipette/definitions/2/general/eight_channel_em/p1000/3_0.json @@ -312,7 +312,7 @@ "shaftDiameter": 4.5, "shaftULperMM": 15.904, "backlashDistance": 0.1, - "quirks": [], + "quirks": ["highSpeed"], "plungerHomingConfigurations": { "current": 1.0, "speed": 30 diff --git a/shared-data/pipette/definitions/2/liquid/eight_channel_em/p1000/default/3_0.json b/shared-data/pipette/definitions/2/liquid/eight_channel_em/p1000/default/3_0.json index 95292a3f98b..52c7b58171d 100644 --- a/shared-data/pipette/definitions/2/liquid/eight_channel_em/p1000/default/3_0.json +++ b/shared-data/pipette/definitions/2/liquid/eight_channel_em/p1000/default/3_0.json @@ -2,7 +2,7 @@ "$otSharedSchema": "#/pipette/schemas/2/pipetteLiquidPropertiesSchema.json", "supportedTips": { "t50": { - "uiMaxFlowRate": 802.9, + "uiMaxFlowRate": 1431.0, "defaultAspirateFlowRate": { "default": 478, "valuesByApiLevel": { "2.14": 478 } @@ -83,7 +83,7 @@ "defaultPushOutVolume": 7 }, "t200": { - "uiMaxFlowRate": 847.9, + "uiMaxFlowRate": 1431.0, "defaultAspirateFlowRate": { "default": 716, "valuesByApiLevel": { "2.14": 716 } @@ -162,7 +162,7 @@ "defaultPushOutVolume": 5 }, "t1000": { - "uiMaxFlowRate": 744.6, + "uiMaxFlowRate": 1431.0, "defaultAspirateFlowRate": { "default": 716, "valuesByApiLevel": { "2.14": 716 } diff --git a/shared-data/python/opentrons_shared_data/liquid_classes/liquid_class_definition.py b/shared-data/python/opentrons_shared_data/liquid_classes/liquid_class_definition.py index 0462ac5c0e4..62add6a32b0 100644 --- a/shared-data/python/opentrons_shared_data/liquid_classes/liquid_class_definition.py +++ b/shared-data/python/opentrons_shared_data/liquid_classes/liquid_class_definition.py @@ -1,7 +1,7 @@ """Python shared data models for liquid class definitions.""" from enum import Enum -from typing import TYPE_CHECKING, Literal, Union, Optional, Dict, Any, Sequence +from typing import TYPE_CHECKING, Literal, Union, Optional, Dict, Any, Sequence, Tuple from pydantic import ( BaseModel, @@ -28,8 +28,8 @@ _NonNegativeNumber = Union[_StrictNonNegativeInt, _StrictNonNegativeFloat] """Non-negative JSON number type, written to preserve lack of decimal point.""" -LiquidHandlingPropertyByVolume = Dict[str, _NonNegativeNumber] -"""Settings for liquid class settings keyed by target aspiration/dispense volume.""" +LiquidHandlingPropertyByVolume = Sequence[Tuple[_NonNegativeNumber, _NonNegativeNumber]] +"""Settings for liquid class settings that are interpolated by volume.""" class PositionReference(Enum): diff --git a/shared-data/python/opentrons_shared_data/pipette/types.py b/shared-data/python/opentrons_shared_data/pipette/types.py index d5315ec12d5..c52e57eb20e 100644 --- a/shared-data/python/opentrons_shared_data/pipette/types.py +++ b/shared-data/python/opentrons_shared_data/pipette/types.py @@ -109,6 +109,7 @@ class Quirks(enum.Enum): dropTipShake = "dropTipShake" doubleDropTip = "doubleDropTip" needsUnstick = "needsUnstick" + highSpeed = "highSpeed" class AvailableUnits(enum.Enum): diff --git a/shared-data/tsconfig-data.json b/shared-data/tsconfig-data.json index 4b9ff960c84..e79657a21f8 100644 --- a/shared-data/tsconfig-data.json +++ b/shared-data/tsconfig-data.json @@ -12,6 +12,7 @@ "deck/**/*.json", "labware/**/*.json", "liquid/**/*.json", + "liquid-class/**/*.json", "command/**/*.json", "commandAnnotation/**/*.json", "gripper/**/*.json", diff --git a/shared-data/tsconfig.json b/shared-data/tsconfig.json index a50e215ee95..57f8970d0c6 100644 --- a/shared-data/tsconfig.json +++ b/shared-data/tsconfig.json @@ -18,6 +18,7 @@ "command", "errors", "liquid/types", + "liquid-class", "commandAnnotation/types", "vite.config.mts" ]