Skip to content

Commit 5f34aa5

Browse files
add move_to_well to dispense_while_tracking command
1 parent 09ff25f commit 5f34aa5

File tree

5 files changed

+94
-32
lines changed

5 files changed

+94
-32
lines changed

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

-15
Original file line numberDiff line numberDiff line change
@@ -339,21 +339,6 @@ def dispense(
339339
well_location=well_location,
340340
)
341341
if dynamic_liquid_tracking:
342-
self._engine_client.execute_command(
343-
cmd.MoveToWellParams(
344-
pipetteId=self._pipette_id,
345-
labwareId=labware_id,
346-
wellName=well_name,
347-
wellLocation=WellLocation(
348-
origin=well_location.origin,
349-
offset=well_location.offset,
350-
volumeOffset=0,
351-
),
352-
minimumZHeight=None,
353-
forceDirect=False,
354-
speed=None,
355-
)
356-
)
357342
self._engine_client.execute_command(
358343
cmd.DispenseWhileTrackingParams(
359344
pipetteId=self._pipette_id,

api/src/opentrons/protocol_api/instrument_context.py

+2
Original file line numberDiff line numberDiff line change
@@ -2543,6 +2543,8 @@ def _handle_aspirate_target(
25432543
if target.location:
25442544
move_to_location = target.location
25452545
meniscus_tracking = target.location.meniscus_tracking
2546+
if meniscus_tracking == types.MeniscusTrackingTarget.START:
2547+
raise ValueError("Cannot aspirate at starting height.")
25462548

25472549
else:
25482550
move_to_location = target.well.bottom(

api/src/opentrons/protocol_engine/commands/dispense_while_tracking.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pydantic import Field
99
from pydantic.json_schema import SkipJsonSchema
1010

11-
from ..state.update_types import CLEAR
11+
from ..state.update_types import CLEAR, StateUpdate
1212
from ..types import CurrentWell, DeckPoint
1313
from .pipetting_common import (
1414
PipetteIdMixin,
@@ -22,6 +22,7 @@
2222
LiquidHandlingWellLocationMixin,
2323
DestinationPositionResult,
2424
StallOrCollisionError,
25+
move_to_well,
2526
)
2627
from .command import (
2728
AbstractCommandImpl,
@@ -32,7 +33,7 @@
3233
)
3334

3435
if TYPE_CHECKING:
35-
from ..execution import PipettingHandler, GantryMover
36+
from ..execution import PipettingHandler, GantryMover, MovementHandler
3637
from ..resources import ModelUtils
3738
from ..state.state import StateView
3839

@@ -82,12 +83,14 @@ def __init__(
8283
pipetting: PipettingHandler,
8384
model_utils: ModelUtils,
8485
gantry_mover: GantryMover,
86+
movement: MovementHandler,
8587
**kwargs: object,
8688
) -> None:
8789
self._state_view = state_view
8890
self._pipetting = pipetting
8991
self._model_utils = model_utils
9092
self._gantry_mover = gantry_mover
93+
self._movement = movement
9194

9295
async def execute(self, params: DispenseWhileTrackingParams) -> _ExecuteReturn:
9396
"""Move to and dispense to the requested well."""
@@ -98,6 +101,28 @@ async def execute(self, params: DispenseWhileTrackingParams) -> _ExecuteReturn:
98101

99102
current_location = self._state_view.pipettes.get_current_location()
100103
current_position = await self._gantry_mover.get_position(params.pipetteId)
104+
current_well = CurrentWell(
105+
pipette_id=params.pipetteId,
106+
labware_id=params.labwareId,
107+
well_name=params.wellName,
108+
)
109+
state_update = StateUpdate()
110+
move_result = await move_to_well(
111+
movement=self._movement,
112+
model_utils=self._model_utils,
113+
pipette_id=params.pipetteId,
114+
labware_id=params.labwareId,
115+
well_name=params.wellName,
116+
well_location=params.wellLocation,
117+
current_well=current_well,
118+
operation_volume=-params.volume,
119+
)
120+
state_update.append(move_result.state_update)
121+
if isinstance(move_result, DefinedErrorData):
122+
return DefinedErrorData(
123+
public=move_result.public, state_update=state_update
124+
)
125+
101126
dispense_result = await dispense_while_tracking(
102127
pipette_id=params.pipetteId,
103128
labware_id=labware_id,

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

+5-6
Original file line numberDiff line numberDiff line change
@@ -1809,12 +1809,11 @@ def get_liquid_handling_z_change(
18091809
initial_height=initial_handling_height,
18101810
volume=operation_volume,
18111811
)
1812-
if isinstance(initial_handling_height, SimulatedProbeResult) or isinstance(
1813-
final_height, SimulatedProbeResult
1814-
):
1815-
raise errors.LiquidHeightUnknownError(
1816-
"liquid handling z change can only be found using real height and volume inputs."
1817-
)
1812+
# this function is only called by
1813+
# HardwarePipetteHandler::aspirate/dispense while_tracking, and shouldn't
1814+
# be reached in the case of a simulated liquid_probe
1815+
assert not isinstance(initial_handling_height, SimulatedProbeResult)
1816+
assert not isinstance(final_height, SimulatedProbeResult)
18181817
return final_height - initial_handling_height
18191818

18201819
def get_well_offset_adjustment(

api/tests/opentrons/protocol_engine/commands/test_dispense_while_tracking.py

+60-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
from opentrons_shared_data.errors.exceptions import PipetteOverpressureError
99

1010
from opentrons.types import Point
11-
from opentrons.protocol_engine.execution import PipettingHandler, GantryMover
11+
from opentrons.protocol_engine.execution import (
12+
PipettingHandler,
13+
GantryMover,
14+
MovementHandler,
15+
)
1216

1317
from opentrons.protocol_engine.commands.command import SuccessData, DefinedErrorData
1418
from opentrons.protocol_engine.commands.dispense_while_tracking import (
@@ -36,6 +40,7 @@ def subject(
3640
pipetting: PipettingHandler,
3741
state_view: StateView,
3842
gantry_mover: GantryMover,
43+
movement: MovementHandler,
3944
model_utils: ModelUtils,
4045
) -> DispenseWhileTrackingImplementation:
4146
"""Build a command implementation."""
@@ -44,20 +49,27 @@ def subject(
4449
state_view=state_view,
4550
gantry_mover=gantry_mover,
4651
model_utils=model_utils,
52+
movement=movement,
4753
)
4854

4955

56+
@pytest.fixture
57+
def movement(decoy: Decoy) -> MovementHandler:
58+
"""Get a mock in the shape of a MovementHandler."""
59+
return decoy.mock(cls=MovementHandler)
60+
61+
5062
@pytest.mark.parametrize(
5163
"location,stateupdateLabware,stateupdateWell",
5264
[
5365
(
5466
CurrentWell(
5567
pipette_id="pipette-id-abc",
56-
labware_id="labware-id-1",
57-
well_name="well-name-1",
68+
labware_id="funky-labware",
69+
well_name="funky-well",
5870
),
59-
"labware-id-1",
60-
"well-name-1",
71+
"funky-labware",
72+
"funky-well",
6173
),
6274
(
6375
CurrentAddressableArea("pipette-id-abc", "addressable-area-1"),
@@ -124,6 +136,25 @@ async def test_dispense_while_tracking_implementation(
124136
decoy.when(await gantry_mover.get_position("pipette-id-abc")).then_return(
125137
Point(1, 2, 3)
126138
)
139+
_well_location = LiquidHandlingWellLocation(
140+
origin=WellOrigin.MENISCUS, offset=WellOffset(x=0.0, y=0.0, z=1.0)
141+
)
142+
_current_well = CurrentWell(
143+
pipette_id="pipette-id-abc", labware_id="funky-labware", well_name="funky-well"
144+
)
145+
decoy.when(
146+
await subject._movement.move_to_well(
147+
pipette_id="pipette-id-abc",
148+
labware_id="funky-labware",
149+
well_name="funky-well",
150+
well_location=_well_location,
151+
current_well=_current_well,
152+
force_direct=False,
153+
minimum_z_height=None,
154+
speed=None,
155+
operation_volume=-123.0,
156+
),
157+
).then_return(Point(x=4, y=5, z=6))
127158

128159
result = await subject.execute(data)
129160

@@ -162,11 +193,11 @@ async def test_dispense_while_tracking_implementation(
162193
(
163194
CurrentWell(
164195
pipette_id="pipette-id",
165-
labware_id="labware-id-1",
166-
well_name="well-name-1",
196+
labware_id="funky-labware",
197+
well_name="funky-well",
167198
),
168-
"labware-id-1",
169-
"well-name-1",
199+
"funky-labware",
200+
"funky-well",
170201
),
171202
(
172203
CurrentAddressableArea("pipette-id", "addressable-area-1"),
@@ -237,6 +268,26 @@ async def test_overpressure_error(
237268
decoy.when(await gantry_mover.get_position(pipette_id)).then_return(position)
238269
decoy.when(state_view.pipettes.get_current_location()).then_return(location)
239270

271+
_well_location = LiquidHandlingWellLocation(
272+
origin=WellOrigin.MENISCUS, offset=WellOffset(x=0.0, y=0.0, z=1.0)
273+
)
274+
_current_well = CurrentWell(
275+
pipette_id="pipette-id", labware_id="funky-labware", well_name="funky-well"
276+
)
277+
decoy.when(
278+
await subject._movement.move_to_well(
279+
pipette_id="pipette-id",
280+
labware_id="funky-labware",
281+
well_name="funky-well",
282+
well_location=_well_location,
283+
current_well=_current_well,
284+
force_direct=False,
285+
minimum_z_height=None,
286+
speed=None,
287+
operation_volume=-50.0,
288+
),
289+
).then_return(Point(x=4, y=5, z=6))
290+
240291
result = await subject.execute(data)
241292

242293
if isinstance(location, CurrentWell):

0 commit comments

Comments
 (0)