Skip to content
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

feat(api): add WellVolumeOffset to WellLocation #16302

Merged
merged 56 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
dd74c58
initial implementation
pmoegenburg Sep 19, 2024
e210544
update
pmoegenburg Sep 19, 2024
0deed9a
updated get_well_position
pmoegenburg Sep 19, 2024
8f713df
updated implementation
pmoegenburg Sep 19, 2024
64fc6e2
update schema 9
pmoegenburg Sep 19, 2024
1bcc578
updated docstring
pmoegenburg Sep 20, 2024
33e6108
updated schema
pmoegenburg Sep 20, 2024
fd93d0e
updated docstring
pmoegenburg Sep 20, 2024
a40b66b
updated schema and formatted
pmoegenburg Sep 20, 2024
add6638
fleshed out height-to-volume-to-height
pmoegenburg Sep 23, 2024
f2b6bc7
created command-specific WellLocations and move_to_well() subfunctions
pmoegenburg Sep 23, 2024
087caaf
added LiquidHandlingWellLocation and PickUpTipWellLocation
pmoegenburg Sep 24, 2024
3d53137
split get_well_position into submethods
pmoegenburg Sep 24, 2024
68981a3
test fixes
pmoegenburg Sep 25, 2024
a9b6dfb
updated typescript
pmoegenburg Sep 25, 2024
8c0f5ce
schema and test fixes
pmoegenburg Sep 25, 2024
2bc1d66
revert typescript work
pmoegenburg Sep 25, 2024
8731a19
cleaned up comments
pmoegenburg Sep 25, 2024
32760c3
updated command schema and formatted
pmoegenburg Sep 25, 2024
957c573
removed comment
pmoegenburg Sep 25, 2024
df07616
added validate_well_position()
pmoegenburg Sep 26, 2024
843a5b5
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Sep 26, 2024
da7f479
added TODO
pmoegenburg Sep 26, 2024
a9ca313
removed TODO and fixed robot-server tavern tests
pmoegenburg Sep 27, 2024
22d2c39
added test, need to fix. added well_location guardrails
pmoegenburg Sep 30, 2024
17ce9b1
refactored geometry methods and added helper function in labware
pmoegenburg Sep 30, 2024
9d559a6
refactored geometry and added validate_dispense_volume_into_well
pmoegenburg Sep 30, 2024
54205b3
updated long doc strings
pmoegenburg Sep 30, 2024
d2af54e
cleaned up comments and doc strings
pmoegenburg Sep 30, 2024
c2a8271
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Sep 30, 2024
ad334cc
updated command schema 9
pmoegenburg Sep 30, 2024
a671aad
refactored geometry and test
pmoegenburg Oct 2, 2024
726ffc1
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Oct 2, 2024
910d940
updated tests
pmoegenburg Oct 9, 2024
a3ac146
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Oct 9, 2024
38afbc3
update needed after merging in edge
pmoegenburg Oct 9, 2024
b688aac
follow-up format and lint
pmoegenburg Oct 9, 2024
f03bcb4
fixed up frustum_helpers and tests
pmoegenburg Oct 10, 2024
67d53a4
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
pmoegenburg Oct 10, 2024
053a47c
updated validate_well_position and command schema 10
pmoegenburg Oct 10, 2024
05a075c
added tiprack check to move_to_well
pmoegenburg Oct 10, 2024
97858d8
updates to relax WellLocation contraints based on PR review
pmoegenburg Oct 11, 2024
4e6a6ba
reverted command schema 9
pmoegenburg Oct 11, 2024
f364bc9
linted, removed test, reverted protocol/models/ work
pmoegenburg Oct 11, 2024
029ecea
eliminated test that doesn't do anything
pmoegenburg Oct 11, 2024
38235fe
Created PickUpTipWellOrigin without MENISCUS enum and updated BlowOut…
pmoegenburg Oct 14, 2024
cedc8b4
added Papi support for Well.meniscus Location
pmoegenburg Oct 15, 2024
7fe0451
reduce PE constraints
pmoegenburg Oct 16, 2024
fc0572f
Papi refactor to eliminate calculations prior to PE execution
pmoegenburg Oct 16, 2024
81da6d0
fixed Papi to not calculate meniscus location prior to PE execution
pmoegenburg Oct 17, 2024
b9aba1f
fixed simulation/analysis
pmoegenburg Oct 17, 2024
618e92f
Hid Location.is_meniscus from docs
pmoegenburg Oct 17, 2024
fb8a20c
added geometry test
pmoegenburg Oct 17, 2024
6feaff0
tidied up
pmoegenburg Oct 17, 2024
936b91c
addressed TODO to more appropriately name exceptions
pmoegenburg Oct 17, 2024
665e815
eliminated unneeded method
pmoegenburg Oct 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def aspirate(
rate: float,
flow_rate: float,
in_place: bool,
is_meniscus: Optional[bool] = None,
) -> None:
"""Aspirate a given volume of liquid from the specified location.
Args:
Expand Down Expand Up @@ -146,13 +147,13 @@ def aspirate(
well_name = well_core.get_name()
labware_id = well_core.labware_id

well_location = (
self._engine_client.state.geometry.get_relative_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
well_location = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
if is_meniscus:
well_location.volumeOffset = "operationVolume"
pipette_movement_conflict.check_safe_for_pipette_movement(
engine_state=self._engine_client.state,
pipette_id=self._pipette_id,
Expand Down Expand Up @@ -237,12 +238,10 @@ def dispense(
well_name = well_core.get_name()
labware_id = well_core.labware_id

well_location = (
self._engine_client.state.geometry.get_relative_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
well_location = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
pipette_movement_conflict.check_safe_for_pipette_movement(
engine_state=self._engine_client.state,
Expand Down Expand Up @@ -416,10 +415,12 @@ def pick_up_tip(
well_name = well_core.get_name()
labware_id = well_core.labware_id

well_location = self._engine_client.state.geometry.get_relative_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
well_location = (
self._engine_client.state.geometry.get_relative_pick_up_tip_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
)
pipette_movement_conflict.check_safe_for_tip_pickup_and_return(
engine_state=self._engine_client.state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
DeckSlotLocation,
OnLabwareLocation,
WellLocation,
LiquidHandlingWellLocation,
PickUpTipWellLocation,
DropTipWellLocation,
)
from opentrons.protocol_engine.types import (
Expand Down Expand Up @@ -66,7 +68,12 @@ def check_safe_for_pipette_movement(
pipette_id: str,
labware_id: str,
well_name: str,
well_location: Union[WellLocation, DropTipWellLocation],
well_location: Union[
WellLocation,
LiquidHandlingWellLocation,
PickUpTipWellLocation,
DropTipWellLocation,
],
) -> None:
"""Check if the labware is safe to move to with a pipette in partial tip configuration.

Expand Down
1 change: 1 addition & 0 deletions api/src/opentrons/protocol_api/core/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def aspirate(
rate: float,
flow_rate: float,
in_place: bool,
is_meniscus: Optional[bool] = None,
) -> None:
"""Aspirate a given volume of liquid from the specified location.
Args:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def aspirate(
rate: float,
flow_rate: float,
in_place: bool,
is_meniscus: Optional[bool] = None,
) -> None:
"""Aspirate a given volume of liquid from the specified location.
Args:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def aspirate(
rate: float,
flow_rate: float,
in_place: bool,
is_meniscus: Optional[bool] = None,
) -> None:
if self.get_current_volume() == 0:
# Make sure we're at the top of the labware and clear of any
Expand Down
3 changes: 3 additions & 0 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ def aspirate(
)
)

is_meniscus: Optional[bool] = None
well: Optional[labware.Well] = None
move_to_location: types.Location
last_location = self._get_last_location_by_api_version()
Expand All @@ -237,6 +238,7 @@ def aspirate(
z=self._well_bottom_clearances.aspirate
)
well = target.well
is_meniscus = target.is_meniscus
if isinstance(target, validation.PointTarget):
move_to_location = target.location
if isinstance(target, (TrashBin, WasteChute)):
Expand Down Expand Up @@ -282,6 +284,7 @@ def aspirate(
rate=rate,
flow_rate=flow_rate,
in_place=target.in_place,
is_meniscus=is_meniscus if is_meniscus is not None else None,
)

return self
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/protocol_api/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def meniscus(self, z: float = 0.0) -> Location:

:meta private:
"""
return Location(self._core.get_meniscus(z_offset=z), self)
return Location(self._core.get_meniscus(z_offset=z), self, True)

@requires_version(2, 8)
def from_center_cartesian(self, x: float, y: float, z: float) -> Point:
Expand Down
8 changes: 7 additions & 1 deletion api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ class WellTarget(NamedTuple):
well: Well
location: Optional[Location]
in_place: bool
is_meniscus: Optional[bool] = None


class PointTarget(NamedTuple):
Expand Down Expand Up @@ -476,7 +477,12 @@ def validate_location(
_, well = target_location.labware.get_parent_labware_and_well()

return (
WellTarget(well=well, location=target_location, in_place=in_place)
WellTarget(
well=well,
location=target_location,
in_place=in_place,
is_meniscus=target_location.is_meniscus,
)
if well is not None
else PointTarget(location=target_location, in_place=in_place)
)
6 changes: 6 additions & 0 deletions api/src/opentrons/protocol_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@
LoadedPipette,
MotorAxis,
WellLocation,
LiquidHandlingWellLocation,
PickUpTipWellLocation,
DropTipWellLocation,
WellOrigin,
DropTipWellOrigin,
PickUpTipWellOrigin,
WellOffset,
ModuleModel,
ModuleDefinition,
Expand Down Expand Up @@ -109,9 +112,12 @@
"LoadedPipette",
"MotorAxis",
"WellLocation",
"LiquidHandlingWellLocation",
"PickUpTipWellLocation",
"DropTipWellLocation",
"WellOrigin",
"DropTipWellOrigin",
"PickUpTipWellOrigin",
"WellOffset",
"ModuleModel",
"ModuleDefinition",
Expand Down
11 changes: 8 additions & 3 deletions api/src/opentrons/protocol_engine/commands/aspirate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
PipetteIdMixin,
AspirateVolumeMixin,
FlowRateMixin,
WellLocationMixin,
LiquidHandlingWellLocationMixin,
BaseLiquidHandlingResult,
DestinationPositionResult,
)
Expand Down Expand Up @@ -38,7 +38,7 @@


class AspirateParams(
PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, WellLocationMixin
PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, LiquidHandlingWellLocationMixin
):
"""Parameters required to aspirate from a specific well."""

Expand Down Expand Up @@ -112,12 +112,17 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn:
well_name=well_name,
)

well_location = params.wellLocation
if well_location.origin == WellOrigin.MENISCUS:
well_location.volumeOffset = "operationVolume"

position = await self._movement.move_to_well(
pipette_id=pipette_id,
labware_id=labware_id,
well_name=well_name,
well_location=params.wellLocation,
well_location=well_location,
current_well=current_well,
operation_volume=-params.volume,
)
deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
state_update.set_pipette_location(
Expand Down
30 changes: 22 additions & 8 deletions api/src/opentrons/protocol_engine/commands/dispense.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
PipetteIdMixin,
DispenseVolumeMixin,
FlowRateMixin,
WellLocationMixin,
LiquidHandlingWellLocationMixin,
BaseLiquidHandlingResult,
DestinationPositionResult,
OverpressureError,
Expand All @@ -30,13 +30,14 @@
if TYPE_CHECKING:
from ..execution import MovementHandler, PipettingHandler
from ..resources import ModelUtils
from ..state.state import StateView


DispenseCommandType = Literal["dispense"]


class DispenseParams(
PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin, WellLocationMixin
PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin, LiquidHandlingWellLocationMixin
pmoegenburg marked this conversation as resolved.
Show resolved Hide resolved
):
"""Payload required to dispense to a specific well."""

Expand All @@ -63,37 +64,50 @@ class DispenseImplementation(AbstractCommandImpl[DispenseParams, _ExecuteReturn]

def __init__(
self,
state_view: StateView,
movement: MovementHandler,
pipetting: PipettingHandler,
model_utils: ModelUtils,
**kwargs: object,
) -> None:
self._state_view = state_view
self._movement = movement
self._pipetting = pipetting
self._model_utils = model_utils

async def execute(self, params: DispenseParams) -> _ExecuteReturn:
"""Move to and dispense to the requested well."""
state_update = StateUpdate()
well_location = params.wellLocation
labware_id = params.labwareId
well_name = params.wellName
volume = params.volume

self._state_view.geometry.validate_dispense_volume_into_well(
labware_id=labware_id,
well_name=well_name,
well_location=well_location,
volume=volume,
)

position = await self._movement.move_to_well(
pipette_id=params.pipetteId,
labware_id=params.labwareId,
well_name=params.wellName,
well_location=params.wellLocation,
labware_id=labware_id,
well_name=well_name,
well_location=well_location,
)
deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z)
state_update.set_pipette_location(
pipette_id=params.pipetteId,
new_labware_id=params.labwareId,
new_well_name=params.wellName,
new_labware_id=labware_id,
new_well_name=well_name,
new_deck_point=deck_point,
)

try:
volume = await self._pipetting.dispense_in_place(
pipette_id=params.pipetteId,
volume=params.volume,
volume=volume,
flow_rate=params.flowRate,
push_out=params.pushOut,
)
Expand Down
35 changes: 26 additions & 9 deletions api/src/opentrons/protocol_engine/commands/move_to_well.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import TYPE_CHECKING, Optional, Type
from typing_extensions import Literal

from ..types import DeckPoint
from ..types import DeckPoint, WellOrigin
from .pipetting_common import (
PipetteIdMixin,
WellLocationMixin,
Expand All @@ -13,9 +13,11 @@
from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData
from ..errors.error_occurrence import ErrorOccurrence
from ..state import update_types
from ..errors import LabwareIsTipRackError

if TYPE_CHECKING:
from ..execution import MovementHandler
from ..state.state import StateView

MoveToWellCommandType = Literal["moveToWell"]

Expand All @@ -37,29 +39,44 @@ class MoveToWellImplementation(
):
"""Move to well command implementation."""

def __init__(self, movement: MovementHandler, **kwargs: object) -> None:
def __init__(
self, state_view: StateView, movement: MovementHandler, **kwargs: object
) -> None:
self._state_view = state_view
self._movement = movement

async def execute(
self, params: MoveToWellParams
) -> SuccessData[MoveToWellResult, None]:
"""Move the requested pipette to the requested well."""
pipette_id = params.pipetteId
labware_id = params.labwareId
well_name = params.wellName
well_location = params.wellLocation

state_update = update_types.StateUpdate()

if (self._state_view.labware.is_tiprack(labware_id)) and (
well_location.origin == WellOrigin.MENISCUS or well_location.volumeOffset
):
raise LabwareIsTipRackError(
"Cannot specify a WellLocation with an origin of MENISCUS or any volumeOffset with movement to a tip rack"
)

x, y, z = await self._movement.move_to_well(
pipette_id=params.pipetteId,
labware_id=params.labwareId,
well_name=params.wellName,
well_location=params.wellLocation,
pipette_id=pipette_id,
labware_id=labware_id,
well_name=well_name,
well_location=well_location,
force_direct=params.forceDirect,
minimum_z_height=params.minimumZHeight,
speed=params.speed,
)
deck_point = DeckPoint.construct(x=x, y=y, z=z)
state_update.set_pipette_location(
pipette_id=params.pipetteId,
new_labware_id=params.labwareId,
new_well_name=params.wellName,
pipette_id=pipette_id,
new_labware_id=labware_id,
new_well_name=well_name,
new_deck_point=deck_point,
)

Expand Down
Loading
Loading