Skip to content

Commit

Permalink
Merge branch 'chore_release-7.1.0' into api-add_left_column_config_re…
Browse files Browse the repository at this point in the history
…strictions
  • Loading branch information
sanni-t committed Dec 18, 2023
2 parents 5f46954 + 2d0bcfd commit 3755977
Show file tree
Hide file tree
Showing 114 changed files with 2,845 additions and 1,496 deletions.
21 changes: 19 additions & 2 deletions api/docs/v2/basic_commands/liquids.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,27 @@ Flex and OT-2 pipettes dispense at :ref:`default flow rates <new-plunger-flow-ra

pipette.dispense(200, plate['B1'], rate=2.0)

.. Removing the 2 notes here from the original. Covered by new revisions.
.. versionadded:: 2.0

.. _push-out-dispense:

Push Out After Dispense
-----------------------

The optional ``push_out`` parameter of ``dispense()`` helps ensure all liquid leaves the tip. Use ``push_out`` for applications that require moving the pipette plunger lower than the default, without performing a full :ref:`blow out <blow-out>`.

For example, this dispense action moves the plunger the equivalent of an additional 5 µL beyond where it would stop if ``push_out`` was set to zero or omitted::

pipette.pick_up_tip()
pipette.aspirate(100, plate['A1'])
pipette.dispense(100, plate['B1'], push_out=5)
pipette.drop_tip()

.. versionadded:: 2.15

.. note::
In version 7.0.2 and earlier of the robot software, you could accomplish a similar result by dispensing a volume greater than what was aspirated into the pipette. In version 7.1.0 and later, the API will return an error. Calculate the difference between the two amounts and use that as the value of ``push_out``.

.. _new-blow-out:

.. _blow-out:
Expand Down
6 changes: 3 additions & 3 deletions api/docs/v2/new_pipette.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ If you're writing a protocol that uses the Flex Gripper, you might think that th
Loading a Flex 96-Channel Pipette
---------------------------------

This code sample loads the Flex 96-Channel Pipette. Because of its size, the Flex 96-Channel Pipette requires the left *and* right pipette mounts. You cannot use this pipette with 1- or 8-Channel Pipette in the same protocol or when these instruments are attached to the robot. To load the 96-Channel Pipette, specify its position as ``mount='left'`` as shown here:
This code sample loads the Flex 96-Channel Pipette. Because of its size, the Flex 96-Channel Pipette requires the left *and* right pipette mounts. You cannot use this pipette with 1- or 8-Channel Pipette in the same protocol or when these instruments are attached to the robot. When loading the 96-Channel Pipette, you can omit the ``mount`` argument from ``load_instrument()`` as shown here:

.. code-block:: python
def run(protocol: protocol_api.ProtocolContext):
left = protocol.load_instrument(
instrument_name='flex_96channel_1000', mount='left')
pipette = protocol.load_instrument(
instrument_name='flex_96channel_1000')
.. versionadded:: 2.15

Expand Down
1 change: 1 addition & 0 deletions api/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Welcome to the v7.1.0 release of the Opentrons robot software! This release incl

### Improved Features

- The Ethernet port on Flex now supports direct connection to a computer.
- Improves aspirate, dispense, and mix behavior with volumes set to zero.
- The `opentrons_simulate` command-line tool now works with all Python API versions.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,6 @@ def remove_tip(self) -> None:
Remove the tip from the pipette (effectively updates the pipette's
critical point)
"""
assert self.has_tip
self._has_tip = False
self._current_tip_length = 0.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,6 @@ def remove_tip(self) -> None:
Remove the tip from the pipette (effectively updates the pipette's
critical point)
"""
assert self.has_tip_length
self._current_tip_length = 0.0
self._has_tip_length = False

Expand Down
9 changes: 9 additions & 0 deletions api/src/opentrons/hardware_control/nozzle_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ def xy_center_offset(self) -> Point:
difference[0] / 2, difference[1] / 2, 0
)

@property
def y_center_offset(self) -> Point:
"""The position in the center of the primary column of the map."""
front_left = next(reversed(list(self.rows.values())))[0]
difference = self.map_store[front_left] - self.map_store[self.back_left]
return self.map_store[self.back_left] + Point(0, difference[1] / 2, 0)

@property
def front_nozzle_offset(self) -> Point:
"""The offset for the front_left nozzle."""
Expand Down Expand Up @@ -319,6 +326,8 @@ def critical_point_with_tip_length(
) -> Point:
if cp_override == CriticalPoint.XY_CENTER:
current_nozzle = self._current_nozzle_configuration.xy_center_offset
elif cp_override == CriticalPoint.Y_CENTER:
current_nozzle = self._current_nozzle_configuration.y_center_offset
elif cp_override == CriticalPoint.FRONT_NOZZLE:
current_nozzle = self._current_nozzle_configuration.front_nozzle_offset
else:
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@ async def _configure_instruments(self) -> None:
"""Configure instruments"""
await self.set_gantry_load(self._gantry_load_from_instruments())
await self.refresh_positions()
await self.reset_tip_detectors()
await self.reset_tip_detectors(False)

async def reset_tip_detectors(
self,
Expand Down
10 changes: 10 additions & 0 deletions api/src/opentrons/hardware_control/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,16 @@ class CriticalPoint(enum.Enum):
back calibration pin slot.
"""

Y_CENTER = enum.auto()
"""
Y_CENTER means the critical point under consideration is at the same X
coordinate as the default nozzle point (i.e. TIP | NOZZLE | FRONT_NOZZLE)
but halfway in between the Y axis bounding box of the pipette - it is the
XY center of the first column in the pipette. It's really only relevant for
the 96; it will produce the same position as XY_CENTER on an eight or one
channel pipette.
"""


class ExecutionState(enum.Enum):
RUNNING = enum.auto()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def check_safe_for_tip_pickup_and_return(
tiprack_parent = engine_state.labware.get_location(labware_id)
if isinstance(tiprack_parent, OnLabwareLocation): # tiprack is on an adapter
is_96_ch_tiprack_adapter = engine_state.labware.get_has_quirk(
labware_id=labware_id, quirk="tiprackAdapterFor96Channel"
labware_id=tiprack_parent.labwareId, quirk="tiprackAdapterFor96Channel"
)
tiprack_height = engine_state.labware.get_dimensions(labware_id).z
adapter_height = engine_state.labware.get_dimensions(tiprack_parent.labwareId).z
Expand Down
28 changes: 20 additions & 8 deletions api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ def drop_tip_in_disposal_location(
disposal_location,
force_direct=False,
speed=None,
alternate_tip_drop=True,
)
self._drop_tip_in_place(home_after=home_after)
self._protocol_core.set_last_location(location=None, mount=self.get_mount())
Expand All @@ -480,13 +481,24 @@ def _move_to_disposal_location(
disposal_location: Union[TrashBin, WasteChute],
force_direct: bool,
speed: Optional[float],
alternate_tip_drop: bool = False,
) -> None:
# TODO (nd, 2023-11-30): give appropriate offset when finalized
# https://opentrons.atlassian.net/browse/RSS-391
offset = AddressableOffsetVector(x=0, y=0, z=0)

if isinstance(disposal_location, TrashBin):
addressable_area_name = disposal_location._addressable_area_name
self._engine_client.move_to_addressable_area_for_drop_tip(
pipette_id=self._pipette_id,
addressable_area_name=addressable_area_name,
offset=offset,
force_direct=force_direct,
speed=speed,
minimum_z_height=None,
alternate_drop_location=alternate_tip_drop,
)

if isinstance(disposal_location, WasteChute):
num_channels = self.get_channels()
addressable_area_name = {
Expand All @@ -495,14 +507,14 @@ def _move_to_disposal_location(
96: "96ChannelWasteChute",
}[num_channels]

self._engine_client.move_to_addressable_area(
pipette_id=self._pipette_id,
addressable_area_name=addressable_area_name,
offset=offset,
force_direct=force_direct,
speed=speed,
minimum_z_height=None,
)
self._engine_client.move_to_addressable_area(
pipette_id=self._pipette_id,
addressable_area_name=addressable_area_name,
offset=offset,
force_direct=force_direct,
speed=speed,
minimum_z_height=None,
)

def _drop_tip_in_place(self, home_after: Optional[bool]) -> None:
self._engine_client.drop_tip_in_place(
Expand Down
5 changes: 5 additions & 0 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ def dispense(
:type rate: float
:param push_out: Continue past the plunger bottom to help ensure all liquid
leaves the tip. Measured in µL. The default value is ``None``.
See :ref:`push-out-dispense` for details.
:type push_out: float
:returns: This instance.
Expand All @@ -341,6 +343,9 @@ def dispense(
``location``, specify it as a keyword argument:
``pipette.dispense(location=plate['A1'])``.
.. versionchanged:: 2.15
Added the ``push_out`` parameter.
"""
if self.api_version < APIVersion(2, 15) and push_out:
raise APIVersionError(
Expand Down
25 changes: 14 additions & 11 deletions api/src/opentrons/protocol_api/protocol_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ def loaded_modules(self) -> Dict[int, ModuleTypes]:
def load_instrument(
self,
instrument_name: str,
mount: Union[Mount, str],
mount: Union[Mount, str, None] = None,
tip_racks: Optional[List[Labware]] = None,
replace: bool = False,
) -> InstrumentContext:
Expand All @@ -831,15 +831,16 @@ def load_instrument(
ensure that the correct instrument is attached in the specified
location.
:param str instrument_name: The name of the instrument model, or a
prefix. For instance, 'p10_single' may be
used to request a P10 single regardless of
the version.
:param mount: The mount in which this instrument should be attached.
:param str instrument_name: Which instrument you want to load. See :ref:`new-pipette-models`
for the valid values.
:param mount: The mount where this instrument should be attached.
This can either be an instance of the enum type
:py:class:`.types.Mount` or one of the strings `'left'`
and `'right'`.
:type mount: types.Mount or str
:py:class:`.types.Mount` or one of the strings ``"left"``
or ``"right"``. If you're loading a Flex 96-Channel Pipette
(``instrument_name="flex_96channel_1000"``), you can leave this unspecified,
since it always occupies both mounts; if you do specify a value, it will be
ignored.
:type mount: types.Mount or str or ``None``
:param tip_racks: A list of tip racks from which to pick tips if
:py:meth:`.InstrumentContext.pick_up_tip` is called
without arguments.
Expand All @@ -850,9 +851,11 @@ def load_instrument(
"""
instrument_name = validation.ensure_lowercase_name(instrument_name)
checked_instrument_name = validation.ensure_pipette_name(instrument_name)
is_96_channel = checked_instrument_name == PipetteNameType.P1000_96
checked_mount = validation.ensure_mount_for_pipette(
mount, checked_instrument_name
)

checked_mount = Mount.LEFT if is_96_channel else validation.ensure_mount(mount)
is_96_channel = checked_instrument_name == PipetteNameType.P1000_96

tip_racks = tip_racks or []

Expand Down
23 changes: 21 additions & 2 deletions api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,27 @@ class InvalidTrashBinLocationError(ValueError):
"""An error raised when attempting to load trash bins in invalid slots."""


def ensure_mount(mount: Union[str, Mount]) -> Mount:
def ensure_mount_for_pipette(
mount: Union[str, Mount, None], pipette: PipetteNameType
) -> Mount:
"""Ensure that an input value represents a valid mount, and is valid for the given pipette."""
if pipette == PipetteNameType.P1000_96:
# Always validate the raw mount input, even if the pipette is a 96-channel and we're not going
# to use the mount value.
if mount is not None:
_ensure_mount(mount)
# Internal layers treat the 96-channel as being on the left mount.
return Mount.LEFT
else:
if mount is None:
raise InvalidPipetteMountError(
f"You must specify a left or right mount to load {pipette.value}."
)
else:
return _ensure_mount(mount)


def _ensure_mount(mount: Union[str, Mount]) -> Mount:
"""Ensure that an input value represents a valid Mount."""
if mount in [Mount.EXTENSION, "extension"]:
# This would cause existing protocols that might be iterating over mount types
Expand Down Expand Up @@ -274,7 +294,6 @@ def ensure_module_model(load_name: str) -> ModuleModel:
def ensure_and_convert_trash_bin_location(
deck_slot: Union[int, str], api_version: APIVersion, robot_type: RobotType
) -> str:

"""Ensure trash bin load location is valid.
Also, convert the deck slot to a valid trash bin addressable area.
Expand Down
26 changes: 26 additions & 0 deletions api/src/opentrons/protocol_engine/clients/sync_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,32 @@ def move_to_addressable_area(

return cast(commands.MoveToAddressableAreaResult, result)

def move_to_addressable_area_for_drop_tip(
self,
pipette_id: str,
addressable_area_name: str,
offset: AddressableOffsetVector,
minimum_z_height: Optional[float],
force_direct: bool,
speed: Optional[float],
alternate_drop_location: Optional[bool],
) -> commands.MoveToAddressableAreaForDropTipResult:
"""Execute a MoveToAddressableArea command and return the result."""
request = commands.MoveToAddressableAreaForDropTipCreate(
params=commands.MoveToAddressableAreaForDropTipParams(
pipetteId=pipette_id,
addressableAreaName=addressable_area_name,
offset=offset,
forceDirect=force_direct,
minimumZHeight=minimum_z_height,
speed=speed,
alternateDropLocation=alternate_drop_location,
)
)
result = self._transport.execute_command(request=request)

return cast(commands.MoveToAddressableAreaForDropTipResult, result)

def move_to_coordinates(
self,
pipette_id: str,
Expand Down
14 changes: 14 additions & 0 deletions api/src/opentrons/protocol_engine/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@
MoveToAddressableAreaCommandType,
)

from .move_to_addressable_area_for_drop_tip import (
MoveToAddressableAreaForDropTip,
MoveToAddressableAreaForDropTipParams,
MoveToAddressableAreaForDropTipCreate,
MoveToAddressableAreaForDropTipResult,
MoveToAddressableAreaForDropTipCommandType,
)

from .wait_for_resume import (
WaitForResume,
WaitForResumeParams,
Expand Down Expand Up @@ -435,6 +443,12 @@
"MoveToAddressableAreaCreate",
"MoveToAddressableAreaResult",
"MoveToAddressableAreaCommandType",
# move to addressable area for drop tip command models
"MoveToAddressableAreaForDropTip",
"MoveToAddressableAreaForDropTipParams",
"MoveToAddressableAreaForDropTipCreate",
"MoveToAddressableAreaForDropTipResult",
"MoveToAddressableAreaForDropTipCommandType",
# wait for resume command models
"WaitForResume",
"WaitForResumeParams",
Expand Down
Loading

0 comments on commit 3755977

Please sign in to comment.