Skip to content

Commit ff38927

Browse files
rm starting_height arg, add get_current_volume
1 parent bac7d54 commit ff38927

File tree

8 files changed

+110
-9
lines changed

8 files changed

+110
-9
lines changed

api/src/opentrons/protocol_api/core/engine/well.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,12 @@ def from_center_cartesian(self, x: float, y: float, z: float) -> Point:
158158

159159
def estimate_liquid_height_after_pipetting(
160160
self,
161-
starting_liquid_height: float,
162161
operation_volume: float,
163162
) -> float:
164163
"""Return an estimate of liquid height after pipetting without raising an error."""
165164
labware_id = self.labware_id
166165
well_name = self._name
166+
starting_liquid_height = self.current_liquid_height()
167167
projected_final_height = self._engine_client.state.geometry.get_well_height_after_liquid_handling_no_error(
168168
labware_id=labware_id,
169169
well_name=well_name,
@@ -179,3 +179,11 @@ def current_liquid_height(self) -> float:
179179
return self._engine_client.state.geometry.get_meniscus_height(
180180
labware_id=labware_id, well_name=well_name
181181
)
182+
183+
def get_well_volume(self) -> float:
184+
"""Return the current volume in a well."""
185+
labware_id = self.labware_id
186+
well_name = self._name
187+
return self._engine_client.state.geometry.get_current_well_volume(
188+
labware_id=labware_id, well_name=well_name
189+
)

api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ def from_center_cartesian(self, x: float, y: float, z: float) -> Point:
120120

121121
def estimate_liquid_height_after_pipetting(
122122
self,
123-
starting_liquid_height: float,
124123
operation_volume: float,
125124
) -> float:
126125
"""Estimate what the liquid height will be after pipetting, without raising an error."""
@@ -130,6 +129,10 @@ def current_liquid_height(self) -> float:
130129
"""Get the current liquid height."""
131130
return 0.0
132131

132+
def get_well_volume(self) -> float:
133+
"""Get the current well volume."""
134+
return 0.0
135+
133136
# TODO(mc, 2022-10-28): is this used and/or necessary?
134137
def __repr__(self) -> str:
135138
"""Use the well's display name as its repr."""

api/src/opentrons/protocol_api/core/well.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ def from_center_cartesian(self, x: float, y: float, z: float) -> Point:
8686
@abstractmethod
8787
def estimate_liquid_height_after_pipetting(
8888
self,
89-
starting_liquid_height: float,
9089
operation_volume: float,
9190
) -> float:
9291
"""Estimate what the liquid height will be after pipetting, without raising an error."""
@@ -95,5 +94,9 @@ def estimate_liquid_height_after_pipetting(
9594
def current_liquid_height(self) -> float:
9695
"""Get the current liquid height."""
9796

97+
@abstractmethod
98+
def get_well_volume(self) -> float:
99+
"""Get the current volume within a well."""
100+
98101

99102
WellCoreType = TypeVar("WellCoreType", bound=AbstractWellCore)

api/src/opentrons/protocol_api/labware.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,16 @@ def load_liquid(self, liquid: Liquid, volume: float) -> None:
305305

306306
@requires_version(2, 21)
307307
def current_liquid_height(self) -> float:
308+
"""Get the current liquid height in a well."""
308309
return self._core.current_liquid_height()
309310

310311
@requires_version(2, 21)
311-
def estimate_liquid_height_after_pipetting(
312-
self, starting_liquid_height: float, operation_volume: float
313-
) -> float:
312+
def current_well_volume(self) -> float:
313+
"""Get the current liquid volume in a well."""
314+
return self._core.get_well_volume()
315+
316+
@requires_version(2, 21)
317+
def estimate_liquid_height_after_pipetting(self, operation_volume: float) -> float:
314318
"""Check the height of the liquid within a well.
315319
316320
:returns: The height, in mm, of the liquid from the deck.
@@ -321,7 +325,6 @@ def estimate_liquid_height_after_pipetting(
321325
"""
322326

323327
projected_final_height = self._core.estimate_liquid_height_after_pipetting(
324-
starting_liquid_height=starting_liquid_height,
325328
operation_volume=operation_volume,
326329
)
327330
return projected_final_height

api/src/opentrons/protocol_engine/errors/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
CommandNotAllowedError,
7575
InvalidLiquidHeightFound,
7676
LiquidHeightUnknownError,
77+
LiquidVolumeUnknownError,
7778
IncompleteLabwareDefinitionError,
7879
IncompleteWellDefinitionError,
7980
OperationLocationNotInWellError,
@@ -167,6 +168,7 @@
167168
"CommandNotAllowedError",
168169
"InvalidLiquidHeightFound",
169170
"LiquidHeightUnknownError",
171+
"LiquidVolumeUnknownError",
170172
"IncompleteLabwareDefinitionError",
171173
"IncompleteWellDefinitionError",
172174
"OperationLocationNotInWellError",

api/src/opentrons/protocol_engine/errors/exceptions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,19 @@ def __init__(
11011101
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
11021102

11031103

1104+
class LiquidVolumeUnknownError(ProtocolEngineError):
1105+
"""Raised when attempting to report an unknown liquid volume."""
1106+
1107+
def __init__(
1108+
self,
1109+
message: Optional[str] = None,
1110+
details: Optional[Dict[str, Any]] = None,
1111+
wrapping: Optional[Sequence[EnumeratedError]] = None,
1112+
) -> None:
1113+
"""Build a LiquidVolumeUnknownError."""
1114+
super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping)
1115+
1116+
11041117
class EStopActivatedError(ProtocolEngineError):
11051118
"""Represents an E-stop event."""
11061119

api/src/opentrons/protocol_engine/state/geometry.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,6 +1486,49 @@ def get_well_offset_adjustment(
14861486
else:
14871487
return initial_handling_height
14881488

1489+
def get_current_well_volume(
1490+
self,
1491+
labware_id: str,
1492+
well_name: str,
1493+
) -> float:
1494+
"""Returns most recently updated volume in specified well."""
1495+
last_updated = self._wells.get_last_liquid_update(labware_id, well_name)
1496+
if last_updated is None:
1497+
raise errors.LiquidHeightUnknownError(
1498+
"Must LiquidProbe or LoadLiquid before specifying WellOrigin.MENISCUS."
1499+
)
1500+
1501+
well_liquid = self._wells.get_well_liquid_info(
1502+
labware_id=labware_id, well_name=well_name
1503+
)
1504+
if (
1505+
well_liquid.probed_height is not None
1506+
and well_liquid.probed_height.height is not None
1507+
and well_liquid.probed_height.last_probed == last_updated
1508+
):
1509+
return self.get_well_volume_at_height(
1510+
labware_id=labware_id,
1511+
well_name=well_name,
1512+
height=well_liquid.probed_height.height,
1513+
)
1514+
elif (
1515+
well_liquid.loaded_volume is not None
1516+
and well_liquid.loaded_volume.volume is not None
1517+
and well_liquid.loaded_volume.last_loaded == last_updated
1518+
):
1519+
return well_liquid.loaded_volume.volume
1520+
elif (
1521+
well_liquid.probed_volume is not None
1522+
and well_liquid.probed_volume.volume is not None
1523+
and well_liquid.probed_volume.last_probed == last_updated
1524+
):
1525+
return well_liquid.probed_volume.volume
1526+
else:
1527+
# This should not happen if there was an update but who knows
1528+
raise errors.LiquidVolumeUnknownError(
1529+
f"Unable to find liquid volume despite an update at {last_updated}."
1530+
)
1531+
14891532
def get_meniscus_height(
14901533
self,
14911534
labware_id: str,

api/tests/opentrons/protocol_api/core/engine/test_well_core.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset
1212
from opentrons.protocol_engine import commands as cmd
1313
from opentrons.protocol_engine.clients import SyncClient as EngineClient
14-
from opentrons.protocol_engine.errors.exceptions import LiquidHeightUnknownError
14+
from opentrons.protocol_engine.errors.exceptions import (
15+
LiquidHeightUnknownError,
16+
LiquidVolumeUnknownError,
17+
)
1518
from opentrons.protocols.api_support.types import APIVersion
1619
from opentrons.protocols.api_support.util import UnsupportedAPIError
1720
from opentrons.types import Point
@@ -256,6 +259,29 @@ def test_current_liquid_height(
256259
subject.current_liquid_height()
257260

258261

262+
def test_current_liquid_volume(
263+
decoy: Decoy, subject: WellCore, mock_engine_client: EngineClient
264+
) -> None:
265+
"""Make sure current_liquid_volume returns the correct value or raises an error."""
266+
fake_volume = 2222.2
267+
decoy.when(
268+
mock_engine_client.state.geometry.get_current_well_volume(
269+
labware_id="labware-id", well_name="well-name"
270+
)
271+
).then_return(fake_volume)
272+
assert subject.get_well_volume() == fake_volume
273+
274+
# make sure that WellCore propagates a LiquidVolumeUnknownError
275+
decoy.when(
276+
mock_engine_client.state.geometry.get_current_well_volume(
277+
labware_id="labware-id", well_name="well-name"
278+
)
279+
).then_raise(LiquidVolumeUnknownError())
280+
281+
with pytest.raises(LiquidVolumeUnknownError):
282+
subject.get_well_volume()
283+
284+
259285
@pytest.mark.parametrize("operation_volume", [0.0, 100, -100, 2, -4, 5])
260286
def test_estimate_liquid_height_after_pipetting(
261287
decoy: Decoy,
@@ -295,6 +321,7 @@ def test_estimate_liquid_height_after_pipetting(
295321
).then_return(fake_well_geometry)
296322
initial_liquid_height = 5.6
297323
fake_final_height = 10000000
324+
decoy.when(subject.current_liquid_height()).then_return(initial_liquid_height)
298325
decoy.when(
299326
mock_engine_client.state.geometry.get_well_height_after_liquid_handling_no_error(
300327
labware_id="labware-id",
@@ -306,7 +333,6 @@ def test_estimate_liquid_height_after_pipetting(
306333

307334
# make sure that no error was raised
308335
final_height = subject.estimate_liquid_height_after_pipetting(
309-
starting_liquid_height=initial_liquid_height,
310336
operation_volume=operation_volume,
311337
)
312338
assert final_height == fake_final_height

0 commit comments

Comments
 (0)