Skip to content

feat(step-generation, app): emit distribute_with_liquid_class for python #18603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 16, 2025

Conversation

jerader
Copy link
Collaborator

@jerader jerader commented Jun 11, 2025

closes AUTH-1099

Overview

This PR introduces distribute_with_liquid_class in distribute compound command in step-generation

Test Plan and Hands on Testing

Review code and test creating a distribute step and uploading to the app. should pass analysis

import json
from contextlib import nullcontext as pd_step
from opentrons import protocol_api, types

metadata = {
    "created": "2025-06-11T16:24:47.170Z",
    "lastModified": "2025-06-11T16:25:37.105Z",
    "protocolDesigner": "8.4.4",
    "source": "Protocol Designer",
}

requirements = {
    "robotType": "Flex",
    "apiLevel": "2.24",
}

def run(protocol: protocol_api.ProtocolContext) -> None:
    # Load Labware:
    tip_rack_1 = protocol.load_labware(
        "opentrons_flex_96_tiprack_50ul",
        location="C2",
        namespace="opentrons",
        version=1,
    )
    well_plate_1 = protocol.load_labware(
        "biorad_96_wellplate_200ul_pcr",
        location="D1",
        namespace="opentrons",
        version=3,
    )

    # Load Pipettes:
    pipette_left = protocol.load_instrument("flex_1channel_50", "left", tip_racks=[tip_rack_1])

    # Load Trash Bins:
    trash_bin_1 = protocol.load_trash_bin("A3")

    # Define Liquids:
    liquid_1 = protocol.define_liquid(
        "h20",
        display_color="#b925ff",
    )

    # Load Liquids:
    well_plate_1["A1"].load_liquid(liquid_1, 10)
    well_plate_1["B1"].load_liquid(liquid_1, 10)
    well_plate_1["C1"].load_liquid(liquid_1, 10)
    well_plate_1["D1"].load_liquid(liquid_1, 10)
    well_plate_1["E1"].load_liquid(liquid_1, 10)
    well_plate_1["F1"].load_liquid(liquid_1, 10)
    well_plate_1["G1"].load_liquid(liquid_1, 10)
    well_plate_1["H1"].load_liquid(liquid_1, 10)

    # Load Liquid Classes:
    water_v1 = protocol.get_liquid_class("water")

    # PROTOCOL STEPS

    # Step 1:
    pipette_left.distribute_with_liquid_class(
        volume=10,
        source=[well_plate_1["A1"]],
        dest=[well_plate_1["F12"], well_plate_1["G12"], well_plate_1["H12"]],
        new_tip="once",
        trash_location=trash_bin_1,
        liquid_class=protocol.define_liquid_class(
            name="distribute_step_1",
            base_liquid_class=water_v1,
            properties={
                "p50_single_flex": {"opentrons/opentrons_flex_96_tiprack_50ul/1": {
                    "aspirate": {
                        "aspirate_position": {
                            "offset": {
                                "x": 0,
                                "y": 0,
                                "z": 2,
                            },
                            "position_reference": "well-bottom",
                        },
                        "flow_rate_by_volume": [(0, 32.2)],
                        "pre_wet": False,
                        "correction_by_volume": [(0, 0)],
                        "delay": {
                            "enabled": True,
                            "duration": 0.2,
                        },
                        "mix": {"enabled": False},
                        "submerge": {
                            "delay": {"enabled": False},
                            "speed": 100,
                            "start_position": {
                                "offset": {
                                    "x": 0,
                                    "y": 0,
                                    "z": 2,
                                },
                                "position_reference": "well-top",
                            },
                        },
                        "retract": {
                            "air_gap_by_volume": [(0, 0)],
                            "delay": {"enabled": False},
                            "end_position": {
                                "offset": {
                                    "x": 0,
                                    "y": 0,
                                    "z": 2,
                                },
                                "position_reference": "well-top",
                            },
                            "speed": 50,
                            "touch_tip": {"enabled": False},
                        },
                    },
                    "dispense": {
                        "dispense_position": {
                            "offset": {
                                "x": 0,
                                "y": 0,
                                "z": 2,
                            },
                            "position_reference": "well-bottom",
                        },
                        "push_out_by_volume": [(0, 2)],
                        "flow_rate_by_volume": [(0, 50)],
                        "correction_by_volume": [(0, 0)],
                        "delay": {
                            "enabled": True,
                            "duration": 0.2,
                        },
                        "submerge": {
                            "delay": {"enabled": False},
                            "speed": 100,
                            "start_position": {
                                "offset": {
                                    "x": 0,
                                    "y": 0,
                                    "z": 2,
                                },
                                "position_reference": "well-top",
                            },
                        },
                        "mix": {"enable": False},
                        "retract": {
                            "air_gap_by_volume": [(0, 0)],
                            "delay": {"enabled": False},
                            "end_position": {
                                "offset": {
                                    "x": 0,
                                    "y": 0,
                                    "z": 2,
                                },
                                "position_reference": "well-top",
                            },
                            "speed": 50,
                            "touch_tip": {"enabled": False},
                            "blowout": {
                                "enabled": True,
                                "location": "trash",
                                "flow_rate": 50,
                            },
                            "conditioning_by_volume": [(0, 5)],
                            "disposal_by_volume": [(0, 5)],
                        },
                    },
                }},
            },
        ),
    )

DESIGNER_APPLICATION = """{"robot":{"model":"OT-3 Standard"},"designerApplication":{"name":"opentrons/protocol-designer","version":"8.5.0","data":{"pipetteTiprackAssignments":{"a6ef8103-01f5-4b11-987a-a0ee157c9db8":["opentrons/opentrons_flex_96_tiprack_50ul/1"]},"dismissedWarnings":{"form":[],"timeline":[]},"ingredients":{"0":{"displayName":"h20","displayColor":"#b925ff","liquidClass":"waterV1","description":null,"liquidGroupId":"0"}},"ingredLocations":{"8ae752f6-c1db-4961-919f-2263bb8c5ede:opentrons/biorad_96_wellplate_200ul_pcr/3":{"A1":{"0":{"volume":10}},"B1":{"0":{"volume":10}},"C1":{"0":{"volume":10}},"D1":{"0":{"volume":10}},"E1":{"0":{"volume":10}},"F1":{"0":{"volume":10}},"G1":{"0":{"volume":10}},"H1":{"0":{"volume":10}}}},"savedStepForms":{"__INITIAL_DECK_SETUP_STEP__":{"stepType":"manualIntervention","id":"__INITIAL_DECK_SETUP_STEP__","labwareLocationUpdate":{"5078453b-c146-4f7e-ae1e-ed3633312d16:opentrons/opentrons_flex_96_tiprack_50ul/1":"C2","8ae752f6-c1db-4961-919f-2263bb8c5ede:opentrons/biorad_96_wellplate_200ul_pcr/3":"D1"},"pipetteLocationUpdate":{"a6ef8103-01f5-4b11-987a-a0ee157c9db8":"left"},"moduleLocationUpdate":{},"trashBinLocationUpdate":{"07d18bcf-d000-43e6-9efc-5e744bc02ae0:trashBin":"cutoutA3"},"wasteChuteLocationUpdate":{},"stagingAreaLocationUpdate":{},"gripperLocationUpdate":{}},"447a44fa-088f-405d-8f2d-197791e7368e":{"id":"447a44fa-088f-405d-8f2d-197791e7368e","stepType":"moveLiquid","stepName":"transfer","stepDetails":"","stepNumber":0,"aspirate_airGap_checkbox":false,"aspirate_airGap_volume":null,"aspirate_delay_checkbox":true,"aspirate_delay_mmFromBottom":null,"aspirate_delay_seconds":"0.2","aspirate_flowRate":"32.2","aspirate_labware":"8ae752f6-c1db-4961-919f-2263bb8c5ede:opentrons/biorad_96_wellplate_200ul_pcr/3","aspirate_mix_checkbox":false,"aspirate_mix_times":"1","aspirate_mix_volume":"50","aspirate_mmFromBottom":2,"aspirate_position_reference":"well-bottom","aspirate_retract_delay_seconds":"0","aspirate_retract_mmFromBottom":2,"aspirate_retract_speed":"50","aspirate_retract_x_position":0,"aspirate_retract_y_position":0,"aspirate_retract_position_reference":"well-top","aspirate_submerge_delay_seconds":"0","aspirate_submerge_speed":"100","aspirate_submerge_mmFromBottom":2,"aspirate_submerge_x_position":0,"aspirate_submerge_y_position":0,"aspirate_submerge_position_reference":"well-top","aspirate_touchTip_checkbox":false,"aspirate_touchTip_mmFromTop":-1,"aspirate_touchTip_speed":"30","aspirate_touchTip_mmFromEdge":"0.5","aspirate_wellOrder_first":"t2b","aspirate_wellOrder_second":"l2r","aspirate_wells_grouped":false,"aspirate_wells":["A1"],"aspirate_x_position":0,"aspirate_y_position":0,"blowout_checkbox":false,"blowout_flowRate":"50","blowout_location":"07d18bcf-d000-43e6-9efc-5e744bc02ae0:trashBin","blowout_z_offset":0,"changeTip":"once","conditioning_checkbox":true,"conditioning_volume":"5","dispense_airGap_checkbox":false,"dispense_airGap_volume":"","dispense_delay_checkbox":true,"dispense_delay_mmFromBottom":null,"dispense_delay_seconds":"0.2","dispense_flowRate":"50","dispense_labware":"8ae752f6-c1db-4961-919f-2263bb8c5ede:opentrons/biorad_96_wellplate_200ul_pcr/3","dispense_mix_checkbox":false,"dispense_mix_times":null,"dispense_mix_volume":null,"dispense_mmFromBottom":2,"dispense_position_reference":"well-bottom","dispense_retract_delay_seconds":"0","dispense_retract_mmFromBottom":2,"dispense_retract_speed":"50","dispense_retract_x_position":0,"dispense_retract_y_position":0,"dispense_retract_position_reference":"well-top","dispense_submerge_delay_seconds":"0","dispense_submerge_speed":"100","dispense_submerge_mmFromBottom":2,"dispense_submerge_x_position":0,"dispense_submerge_y_position":0,"dispense_submerge_position_reference":"well-top","dispense_touchTip_checkbox":false,"dispense_touchTip_mmFromTop":-1,"dispense_touchTip_speed":"30","dispense_touchTip_mmFromEdge":"0.5","dispense_wellOrder_first":"t2b","dispense_wellOrder_second":"l2r","dispense_wells":["F12","G12","H12"],"dispense_x_position":0,"dispense_y_position":0,"disposalVolume_checkbox":true,"disposalVolume_volume":"5","dropTip_location":"07d18bcf-d000-43e6-9efc-5e744bc02ae0:trashBin","liquidClassesSupported":true,"liquidClass":"waterV1","nozzles":null,"path":"multiDispense","pipette":"a6ef8103-01f5-4b11-987a-a0ee157c9db8","preWetTip":false,"pushOut_checkbox":true,"pushOut_volume":"2","tipRack":"opentrons/opentrons_flex_96_tiprack_50ul/1","volume":"10"}},"orderedStepIds":["447a44fa-088f-405d-8f2d-197791e7368e"],"pipettes":{"a6ef8103-01f5-4b11-987a-a0ee157c9db8":{"pipetteName":"p50_single_flex"}},"modules":{},"labware":{"5078453b-c146-4f7e-ae1e-ed3633312d16:opentrons/opentrons_flex_96_tiprack_50ul/1":{"displayName":"Opentrons Flex 96 Tip Rack 50 µL","labwareDefURI":"opentrons/opentrons_flex_96_tiprack_50ul/1"},"8ae752f6-c1db-4961-919f-2263bb8c5ede:opentrons/biorad_96_wellplate_200ul_pcr/3":{"displayName":"Bio-Rad 96 Well Plate 200 µL PCR","labwareDefURI":"opentrons/biorad_96_wellplate_200ul_pcr/3"}}}},"metadata":{"protocolName":"","author":"","description":"","source":"Protocol Designer","created":1749659087170,"lastModified":1749659137105}}"""

Changelog

  • update getCustomLiquidClassProperties to account for the distribute args disposalVolume and conditioningVolume
  • wire up distribute_with_liquid_class in distribute
  • add some unit tests to test the different args
  • fix for quick transfer

Risk assessment

low, behind ff

@jerader jerader added the DO NOT MERGE Indicates a PR should not be merged, even if there's a shiny green merge button available label Jun 11, 2025
Copy link

codecov bot commented Jun 11, 2025

Codecov Report

Attention: Patch coverage is 88.81119% with 16 lines in your changes missing coverage. Please review.

Project coverage is 23.74%. Comparing base (71e6b93) to head (616114b).
Report is 1 commits behind head on chore_release-pd-8.5.0.

Files with missing lines Patch % Lines
...eration/src/commandCreators/compound/distribute.ts 87.39% 15 Missing ⚠️
...organisms/ODD/QuickTransferFlow/utils/pythonDef.ts 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                    Coverage Diff                     @@
##           chore_release-pd-8.5.0   #18603      +/-   ##
==========================================================
- Coverage                   25.88%   23.74%   -2.14%     
==========================================================
  Files                        3279     3233      -46     
  Lines                      284277   279751    -4526     
  Branches                    28648    28095     -553     
==========================================================
- Hits                        73579    66440    -7139     
- Misses                     210671   213284    +2613     
  Partials                       27       27              
Flag Coverage Δ
protocol-designer 19.11% <78.32%> (-0.14%) ⬇️
step-generation 5.17% <87.41%> (+0.28%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...c/timelineMiddleware/generateRobotStateTimeline.ts 78.30% <100.00%> (+3.30%) ⬆️
step-generation/src/utils/liquidClassUtils.ts 99.51% <100.00%> (-0.49%) ⬇️
...organisms/ODD/QuickTransferFlow/utils/pythonDef.ts 0.00% <0.00%> (ø)
...eration/src/commandCreators/compound/distribute.ts 73.46% <87.39%> (+15.70%) ⬆️

... and 258 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jerader jerader removed the DO NOT MERGE Indicates a PR should not be merged, even if there's a shiny green merge button available label Jun 13, 2025
@jerader jerader marked this pull request as ready for review June 13, 2025 15:57
@jerader jerader requested review from a team as code owners June 13, 2025 15:57
@jerader jerader requested review from TamarZanzouri and ddcc4 and removed request for a team June 13, 2025 15:57
"x": 0,
"y": 0,
"z": 0,
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

You might have to update your test after I checked in the change to generate more compact code. (This line would probably be something like "offset": {"x": 0, "y": 0, "z": 0},

`# ${upperCase(stepArgs?.commandCreatorFnName)} STEP\n\n` +
nonLoadCommands +
`\n` +
finalDropTipCommand
Copy link
Collaborator

Choose a reason for hiding this comment

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

So after this change, the drop tip will always be handled by transfer/consolidate/distribute_with_liquid_class() now?

Copy link
Collaborator Author

@jerader jerader Jun 16, 2025

Choose a reason for hiding this comment

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

correct! since QT does not support mix()

@jerader jerader merged commit 237544a into chore_release-pd-8.5.0 Jun 16, 2025
44 checks passed
@jerader jerader deleted the sg_distribute-liquid-class branch June 16, 2025 12:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants