@@ -1723,6 +1723,7 @@ async def aspirate(
17231723 liquid_surfaces_no_lld : Optional [List [float ]] = None ,
17241724 # PLR:
17251725 probe_liquid_height : bool = False ,
1726+ auto_surface_following_distance : bool = False ,
17261727 # remove >2026-01
17271728 mix_volume : Optional [List [float ]] = None ,
17281729 mix_cycles : Optional [List [int ]] = None ,
@@ -1780,6 +1781,7 @@ async def aspirate(
17801781 liquid_surface_no_lld: Liquid surface at function without LLD [mm]. Must be between 0 and 360. Defaults to well bottom + liquid height. Should use absolute z.
17811782
17821783 probe_liquid_height: PLR-specific parameter. If True, probe the liquid height using cLLD before aspirating to set the liquid_height of every operation instead of using the default 0. Liquid heights must not be set when using this function.
1784+ auto_surface_following_distance: automatically compute the surface following distance based on the container height<->volume functions. Requires liquid height to be specified or `probe_liquid_height=True`.
17831785 """
17841786
17851787 # # # TODO: delete > 2026-01 # # #
@@ -1867,7 +1869,6 @@ async def aspirate(
18671869 immersion_depth = [
18681870 im * (- 1 if immersion_depth_direction [i ] else 1 ) for i , im in enumerate (immersion_depth )
18691871 ]
1870- surface_following_distance = _fill_in_defaults (surface_following_distance , [0.0 ] * n )
18711872 flow_rates = [
18721873 op .flow_rate or (hlc .aspiration_flow_rate if hlc is not None else 100.0 )
18731874 for op , hlc in zip (ops , hamilton_liquid_classes )
@@ -1953,6 +1954,48 @@ async def aspirate(
19531954 wb + lh for wb , lh in zip (well_bottoms , liquid_heights )
19541955 ]
19551956
1957+ if auto_surface_following_distance :
1958+ if any (op .liquid_height is None for op in ops ) and not probe_liquid_height :
1959+ raise ValueError (
1960+ "To use auto_surface_following_distance all liquid heights must be set or probe_liquid_height must be True."
1961+ )
1962+
1963+ if any (not op .resource .supports_compute_height_volume_functions () for op in ops ):
1964+ raise ValueError (
1965+ "automatic_surface_following can only be used with containers that support height<->volume functions."
1966+ )
1967+
1968+ current_volumes = [
1969+ op .resource .compute_volume_from_height (liquid_heights [i ]) for i , op in enumerate (ops )
1970+ ]
1971+
1972+ # compute new liquid_height after aspiration
1973+ liquid_height_after_aspiration = [
1974+ op .resource .compute_height_from_volume (current_volumes [i ] - op .volume )
1975+ for i , op in enumerate (ops )
1976+ ]
1977+
1978+ # compute new surface_following_distance
1979+ surface_following_distance = [
1980+ liquid_heights [i ] - liquid_height_after_aspiration [i ]
1981+ for i in range (len (liquid_height_after_aspiration ))
1982+ ]
1983+ else :
1984+ surface_following_distance = _fill_in_defaults (surface_following_distance , [0.0 ] * n )
1985+
1986+ # check if the surface_following_distance would fall below the minimum height
1987+ if any (
1988+ ops [i ].resource .get_absolute_location (z = "cavity_bottom" ).z
1989+ + liquid_heights [i ]
1990+ - surface_following_distance [i ]
1991+ < minimum_height [i ]
1992+ for i in range (n )
1993+ ):
1994+ raise ValueError (
1995+ f"automatic_surface_following would result in a surface_following_distance that goes below the minimum_height. "
1996+ f"Well bottom: { well_bottoms [i ]} , surface_following_distance: { surface_following_distance [i ]} , minimum_height: { minimum_height [i ]} "
1997+ )
1998+
19561999 try :
19572000 return await self .aspirate_pip (
19582001 aspiration_type = [0 for _ in range (n )],
@@ -2052,6 +2095,7 @@ async def dispense(
20522095 empty : Optional [List [bool ]] = None , # truly "empty", does not exist in liquid editor, dm4
20532096 # PLR specific
20542097 probe_liquid_height : bool = False ,
2098+ auto_surface_following_distance : bool = False ,
20552099 # remove in the future
20562100 immersion_depth_direction : Optional [List [int ]] = None ,
20572101 mix_volume : Optional [List [float ]] = None ,
@@ -2108,6 +2152,7 @@ async def dispense(
21082152 documentation. Dispense mode 4.
21092153
21102154 probe_liquid_height: PLR-specific parameter. If True, probe the liquid height using cLLD before aspirating to set the liquid_height of every operation instead of using the default 0. Liquid heights must not be set when using this function.
2155+ auto_surface_following_distance: automatically compute the surface following distance based on the container height<->volume functions. Requires liquid height to be specified or `probe_liquid_height=True`.
21112156 """
21122157
21132158 n = len (ops )
@@ -2203,7 +2248,6 @@ async def dispense(
22032248 immersion_depth = [
22042249 im * (- 1 if immersion_depth_direction [i ] else 1 ) for i , im in enumerate (immersion_depth )
22052250 ]
2206- surface_following_distance = _fill_in_defaults (surface_following_distance , [0.0 ] * n )
22072251 flow_rates = [
22082252 op .flow_rate or (hlc .dispense_flow_rate if hlc is not None else 120.0 )
22092253 for op , hlc in zip (ops , hamilton_liquid_classes )
@@ -2271,6 +2315,35 @@ async def dispense(
22712315 else :
22722316 liquid_heights = [op .liquid_height or 0 for op in ops ]
22732317
2318+ if auto_surface_following_distance :
2319+ if any (op .liquid_height is None for op in ops ) and not probe_liquid_height :
2320+ raise ValueError (
2321+ "To use auto_surface_following_distance all liquid heights must be set or probe_liquid_height must be True."
2322+ )
2323+
2324+ if any (not op .resource .supports_compute_height_volume_functions () for op in ops ):
2325+ raise ValueError (
2326+ "automatic_surface_following can only be used with containers that support height<->volume functions."
2327+ )
2328+
2329+ current_volumes = [
2330+ op .resource .compute_volume_from_height (liquid_heights [i ]) for i , op in enumerate (ops )
2331+ ]
2332+
2333+ # compute new liquid_height after aspiration
2334+ liquid_height_after_aspiration = [
2335+ op .resource .compute_height_from_volume (current_volumes [i ] + op .volume )
2336+ for i , op in enumerate (ops )
2337+ ]
2338+
2339+ # compute new surface_following_distance
2340+ surface_following_distance = [
2341+ liquid_height_after_aspiration [i ] - liquid_heights [i ]
2342+ for i in range (len (liquid_height_after_aspiration ))
2343+ ]
2344+ else :
2345+ surface_following_distance = _fill_in_defaults (surface_following_distance , [0.0 ] * n )
2346+
22742347 liquid_surfaces_no_lld = liquid_surface_no_lld or [
22752348 wb + lh for wb , lh in zip (well_bottoms , liquid_heights )
22762349 ]
0 commit comments