diff --git a/api/src/opentrons/protocol_api/core/engine/well.py b/api/src/opentrons/protocol_api/core/engine/well.py index e18bdb8ecf6a..964e1f7576c1 100644 --- a/api/src/opentrons/protocol_api/core/engine/well.py +++ b/api/src/opentrons/protocol_api/core/engine/well.py @@ -3,7 +3,7 @@ from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN -from opentrons.types import Point +from opentrons.types import Point, Mount, MountType from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset from opentrons.protocol_engine import commands as cmd @@ -13,6 +13,7 @@ SimulatedProbeResult, LiquidTrackingType, ) +from opentrons.protocol_engine.errors import PipetteNotAttachedError from . import point_calculations from . import stringify @@ -181,15 +182,25 @@ def from_center_cartesian(self, x: float, y: float, z: float) -> Point: def estimate_liquid_height_after_pipetting( self, + mount: Mount | str, operation_volume: float, ) -> LiquidTrackingType: """Return an estimate of liquid height after pipetting without raising an error.""" labware_id = self.labware_id well_name = self._name + if isinstance(mount, Mount): + mount_type = MountType.from_hw_mount(mount) + else: + mount_type = MountType(mount) + pipette_from_mount = self._engine_client.state.pipettes.get_by_mount(mount_type) + if pipette_from_mount is None: + raise PipetteNotAttachedError(f"No pipette present on mount {mount}") + pipette_id = pipette_from_mount.id starting_liquid_height = self.current_liquid_height() projected_final_height = self._engine_client.state.geometry.get_well_height_after_liquid_handling_no_error( labware_id=labware_id, well_name=well_name, + pipette_id=pipette_id, initial_height=starting_liquid_height, volume=operation_volume, ) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py index e0a12039c518..671409c9587a 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py @@ -5,7 +5,7 @@ from opentrons.protocols.api_support.util import APIVersionError -from opentrons.types import Point +from opentrons.types import Point, Mount from opentrons.protocol_engine.types.liquid_level_detection import ( SimulatedProbeResult, @@ -129,6 +129,7 @@ def from_center_cartesian(self, x: float, y: float, z: float) -> Point: def estimate_liquid_height_after_pipetting( self, + mount: Mount | str, operation_volume: float, ) -> LiquidTrackingType: """Estimate what the liquid height will be after pipetting, without raising an error.""" diff --git a/api/src/opentrons/protocol_api/core/well.py b/api/src/opentrons/protocol_api/core/well.py index d02cea5cb24b..02fe1f47f424 100644 --- a/api/src/opentrons/protocol_api/core/well.py +++ b/api/src/opentrons/protocol_api/core/well.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from typing import TypeVar, Optional, Union -from opentrons.types import Point +from opentrons.types import Point, Mount from opentrons.protocol_engine.types import LiquidTrackingType from .._liquid import Liquid @@ -91,6 +91,7 @@ def from_center_cartesian(self, x: float, y: float, z: float) -> Point: @abstractmethod def estimate_liquid_height_after_pipetting( self, + mount: Mount | str, operation_volume: float, ) -> LiquidTrackingType: """Estimate what the liquid height will be after pipetting, without raising an error.""" diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 1d2da5739c96..4975c848b9b8 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -606,7 +606,20 @@ def blow_out( "Blow_out being performed on a tiprack. " "Please re-check your code" ) - move_to_location = target.location or target.well.top() + if target.location: + # because the lower levels of blowout don't handle LiquidHandlingWellLocation and + # there is no "operation_volume" for blowout we need to convert the relative location + # given with a .meniscus to an absolute point. To maintain the meniscus behavior + # we can just add the offset to the current liquid height. + if target.location.meniscus_tracking: + move_to_location = target.well.bottom( + target.well.current_liquid_height() # type: ignore [arg-type] + + target.location.point.z + ) + else: + move_to_location = target.location + else: + move_to_location = target.well.top() well = target.well elif isinstance(target, validation.PointTarget): move_to_location = target.location @@ -2698,7 +2711,8 @@ def measure_liquid_height(self, well: labware.Well) -> LiquidTrackingType: """ self._raise_if_pressure_not_supported_by_pipette() loc = well.top() - return self._core.liquid_probe_without_recovery(well._core, loc) + self._core.liquid_probe_with_recovery(well._core, loc) + return well.current_liquid_height() def _raise_if_configuration_not_supported_by_pipette( self, style: NozzleLayout diff --git a/api/src/opentrons/protocol_api/labware.py b/api/src/opentrons/protocol_api/labware.py index 51f1301bf7b3..6580c004fa33 100644 --- a/api/src/opentrons/protocol_api/labware.py +++ b/api/src/opentrons/protocol_api/labware.py @@ -39,6 +39,7 @@ Point, NozzleMapInterface, MeniscusTrackingTarget, + Mount, ) from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support.util import ( @@ -348,6 +349,7 @@ def current_liquid_volume(self) -> LiquidTrackingType: @requires_version(2, 21) def estimate_liquid_height_after_pipetting( self, + mount: Mount | str, operation_volume: float, ) -> LiquidTrackingType: """Check the height of the liquid within a well. @@ -360,7 +362,7 @@ def estimate_liquid_height_after_pipetting( """ projected_final_height = self._core.estimate_liquid_height_after_pipetting( - operation_volume=operation_volume, + operation_volume=operation_volume, mount=mount ) return projected_final_height diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index b503f4ae69f4..1d21542411dc 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -199,6 +199,7 @@ async def aspirate_while_tracking( labware_id=labware_id, well_name=well_name, operation_volume=volume * -1, + pipette_id=pipette_id, ) if isinstance(aspirate_z_distance, SimulatedProbeResult): raise InvalidLiquidHeightFound( @@ -234,6 +235,7 @@ async def dispense_while_tracking( labware_id=labware_id, well_name=well_name, operation_volume=volume, + pipette_id=pipette_id, ) if isinstance(dispense_z_distance, SimulatedProbeResult): raise InvalidLiquidHeightFound( diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index 4ba4c35a32f2..84f66844c318 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -520,6 +520,7 @@ def get_well_position( well_location=well_location, well_depth=well_depth, operation_volume=operation_volume, + pipette_id=pipette_id, ) if not isinstance(offset_adjustment, SimulatedProbeResult): offset = offset.model_copy(update={"z": offset.z + offset_adjustment}) @@ -1844,6 +1845,7 @@ def get_liquid_handling_z_change( self, labware_id: str, well_name: str, + pipette_id: str, operation_volume: float, ) -> float: """Get the change in height from a liquid handling operation.""" @@ -1853,6 +1855,7 @@ def get_liquid_handling_z_change( final_height = self.get_well_height_after_liquid_handling( labware_id=labware_id, well_name=well_name, + pipette_id=pipette_id, initial_height=initial_handling_height, volume=operation_volume, ) @@ -1873,6 +1876,7 @@ def get_well_offset_adjustment( well_name: str, well_location: WellLocationType, well_depth: float, + pipette_id: Optional[str] = None, operation_volume: Optional[float] = None, ) -> LiquidTrackingType: """Return a z-axis distance that accounts for well handling height and operation volume. @@ -1906,9 +1910,14 @@ def get_well_offset_adjustment( volume = well_location.volumeOffset if volume: + if pipette_id is None: + raise ValueError( + "cannot get liquid handling offset without pipette id." + ) liquid_height_after = self.get_well_height_after_liquid_handling( labware_id=labware_id, well_name=well_name, + pipette_id=pipette_id, initial_height=initial_handling_height, volume=volume, ) @@ -2030,6 +2039,7 @@ def get_well_height_after_liquid_handling( self, labware_id: str, well_name: str, + pipette_id: str, initial_height: LiquidTrackingType, volume: float, ) -> LiquidTrackingType: @@ -2044,7 +2054,14 @@ def get_well_height_after_liquid_handling( initial_volume = find_volume_at_well_height( target_height=initial_height, well_geometry=well_geometry ) - final_volume = initial_volume + volume + final_volume = initial_volume + ( + volume + * self.get_nozzles_per_well( + labware_id=labware_id, + target_well_name=well_name, + pipette_id=pipette_id, + ) + ) return find_height_at_well_volume( target_volume=final_volume, well_geometry=well_geometry ) @@ -2058,6 +2075,7 @@ def get_well_height_after_liquid_handling_no_error( self, labware_id: str, well_name: str, + pipette_id: str, initial_height: LiquidTrackingType, volume: float, ) -> LiquidTrackingType: @@ -2073,7 +2091,14 @@ def get_well_height_after_liquid_handling_no_error( initial_volume = find_volume_at_well_height( target_height=initial_height, well_geometry=well_geometry ) - final_volume = initial_volume + volume + final_volume = initial_volume + ( + volume + * self.get_nozzles_per_well( + labware_id=labware_id, + target_well_name=well_name, + pipette_id=pipette_id, + ) + ) well_volume = find_height_at_well_volume( target_volume=final_volume, well_geometry=well_geometry, diff --git a/api/src/opentrons/protocol_engine/types/__init__.py b/api/src/opentrons/protocol_engine/types/__init__.py index 435d23dcca61..9d22d8be216b 100644 --- a/api/src/opentrons/protocol_engine/types/__init__.py +++ b/api/src/opentrons/protocol_engine/types/__init__.py @@ -135,6 +135,7 @@ WellInfoSummary, WellLiquidInfo, LiquidTrackingType, + SimulatedProbeResult, ) from .liquid_handling import FlowRates from .labware_movement import LabwareMovementStrategy, LabwareMovementOffsetData @@ -280,6 +281,7 @@ "WellInfoSummary", "WellLiquidInfo", "LiquidTrackingType", + "SimulatedProbeResult", # Liquid handling "FlowRates", # Labware movement diff --git a/api/src/opentrons/protocol_engine/types/liquid_level_detection.py b/api/src/opentrons/protocol_engine/types/liquid_level_detection.py index 235add1caf16..4d6bd82329e4 100644 --- a/api/src/opentrons/protocol_engine/types/liquid_level_detection.py +++ b/api/src/opentrons/protocol_engine/types/liquid_level_detection.py @@ -1,9 +1,10 @@ """Protocol Engine types to do with liquid level detection.""" + from __future__ import annotations from dataclasses import dataclass from datetime import datetime -from typing import Optional, List -from pydantic import BaseModel, model_serializer, field_validator +from typing import Optional, List, Any +from pydantic import BaseModel, model_serializer, model_validator class SimulatedProbeResult(BaseModel): @@ -17,6 +18,14 @@ def serialize_model(self) -> str: """Serialize instances of this class as a string.""" return "SimulatedProbeResult" + @model_validator(mode="before") + @classmethod + def validate_model(cls, data: object) -> Any: + """Handle deserializing from a simulated probe result.""" + if isinstance(data, str) and data == "SimulatedProbeResult": + return {} + return data + def __add__( self, other: float | SimulatedProbeResult ) -> float | SimulatedProbeResult: @@ -75,7 +84,9 @@ def simulate_probed_aspirate_dispense(self, volume: float) -> None: self.operations_after_probe.append(volume) -LiquidTrackingType = SimulatedProbeResult | float +# Work around https://github.com/pydantic/pydantic/issues/6830 - do not change the order of +# this union +LiquidTrackingType = float | SimulatedProbeResult class LoadedVolumeInfo(BaseModel): @@ -104,23 +115,6 @@ class ProbedVolumeInfo(BaseModel): class WellInfoSummary(BaseModel): """Payload for a well's liquid info in StateSummary.""" - # TODO(cm): 3/21/25: refactor SimulatedLiquidProbe in a way that - # doesn't require models like this one that are just using it to - # need a custom validator - @field_validator("probed_height", "probed_volume", mode="before") - @classmethod - def validate_simulated_probe_result( - cls, input_val: object - ) -> LiquidTrackingType | None: - """Return the appropriate input to WellInfoSummary from json data.""" - if input_val is None: - return None - if isinstance(input_val, LiquidTrackingType): - return input_val - if isinstance(input_val, str) and input_val == "SimulatedProbeResult": - return SimulatedProbeResult() - raise ValueError(f"Invalid input value {input_val} to WellInfoSummary") - labware_id: str well_name: str loaded_volume: Optional[float] = None diff --git a/api/tests/opentrons/protocol_api/core/engine/test_well_core.py b/api/tests/opentrons/protocol_api/core/engine/test_well_core.py index a45ba03aac22..cad3a1fff8c9 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_well_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_well_core.py @@ -10,9 +10,15 @@ RectangularWellDefinition2, CircularWellDefinition2, ) +from opentrons_shared_data.pipette.types import PipetteNameType from opentrons.protocol_api import MAX_SUPPORTED_VERSION -from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset +from opentrons.protocol_engine import ( + WellLocation, + WellOrigin, + WellOffset, + LoadedPipette, +) from opentrons.protocol_engine import commands as cmd from opentrons.protocol_engine.clients import SyncClient as EngineClient from opentrons.protocol_engine.errors.exceptions import ( @@ -21,7 +27,7 @@ ) from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support.util import UnsupportedAPIError -from opentrons.types import Point +from opentrons.types import Point, Mount, MountType from opentrons_shared_data.labware.labware_definition import ( InnerWellGeometry, ConicalFrustum, @@ -312,11 +318,13 @@ def test_current_liquid_volume( @pytest.mark.parametrize("operation_volume", [0.0, 100, -100, 2, -4, 5]) +@pytest.mark.parametrize("mount", [Mount.LEFT, "left"]) def test_estimate_liquid_height_after_pipetting( decoy: Decoy, subject: WellCore, mock_engine_client: EngineClient, operation_volume: float, + mount: Mount | str, ) -> None: """Make sure estimate_liquid_height_after_pipetting returns the correct value and does not raise an error.""" fake_well_geometry = InnerWellGeometry( @@ -355,14 +363,24 @@ def test_estimate_liquid_height_after_pipetting( mock_engine_client.state.geometry.get_well_height_after_liquid_handling_no_error( labware_id="labware-id", well_name="well-name", + pipette_id="pipette-id", initial_height=initial_liquid_height, volume=operation_volume, ) ).then_return(fake_final_height) + decoy.when( + mock_engine_client.state.pipettes.get_by_mount(MountType.LEFT) + ).then_return( + LoadedPipette( + id="pipette-id", + pipetteName=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + ) # make sure that no error was raised final_height = subject.estimate_liquid_height_after_pipetting( - operation_volume=operation_volume, + operation_volume=operation_volume, mount=mount ) assert final_height == fake_final_height diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 6b8fc4648b1f..9ac1aae69fd6 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -59,7 +59,13 @@ from opentrons.hardware_control.nozzle_manager import NozzleMap from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute -from opentrons.types import Location, Mount, Point, NozzleMapInterface +from opentrons.types import ( + Location, + Mount, + Point, + NozzleMapInterface, + MeniscusTrackingTarget, +) from opentrons_shared_data.pipette.pipette_definition import ValidNozzleMaps from opentrons_shared_data.errors.exceptions import ( @@ -553,6 +559,53 @@ def test_blow_out_to_well_location( ) +def test_blow_out_to_well_meniscus_location( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + """It should blow out to a well location.""" + liquid_height = 10.0 + well_bottom = Point(2, 2, 2) + relative_height = 3 + mock_well = decoy.mock(cls=Well) + input_location_absolute = Location( + point=well_bottom + Point(0, 0, liquid_height) + Point(0, 0, relative_height), + labware=mock_well, + ) + decoy.when(mock_well.current_liquid_height()).then_return(liquid_height) + decoy.when(mock_well.bottom(liquid_height + relative_height)).then_return( + Location( + point=well_bottom + Point(0, 0, liquid_height + relative_height), + labware=mock_well, + ) + ) + + input_location = Location( + point=Point(0, 0, relative_height), + labware=mock_well, + _meniscus_tracking=MeniscusTrackingTarget.END, + ) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=input_location, in_place=False)) + + subject.blow_out(location=input_location) + + mock_instrument_core.blow_out( + location=input_location_absolute, well_core=mock_well._core, in_place=False + ) + + def test_blow_out_to_location( decoy: Decoy, mock_instrument_core: InstrumentCore, @@ -1493,8 +1546,15 @@ def test_measure_liquid_height( original_error=lnfe, message=f"{lnfe.errorType}: {lnfe.detail}", ) + decoy.when(mock_well.current_liquid_height()).then_return(123) decoy.when( - mock_instrument_core.liquid_probe_without_recovery( + mock_instrument_core.liquid_probe_with_recovery( + mock_well._core, mock_well.top() + ) + ) + assert subject.measure_liquid_height(mock_well) == 123 + decoy.when( + mock_instrument_core.liquid_probe_with_recovery( mock_well._core, mock_well.top() ) ).then_raise(errorToRaise) diff --git a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py index e94dc074d5d7..8fd017ec194a 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py @@ -332,7 +332,10 @@ async def test_hw_aspirate_while_tracking( decoy.when( mock_state_view.geometry.get_liquid_handling_z_change( - labware_id="labware-id", well_name="A1", operation_volume=-25.0 + labware_id="labware-id", + well_name="A1", + pipette_id="pipette_id", + operation_volume=-25.0, ) ).then_return(4.544) diff --git a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py index c29cb066e563..4e301b3a007a 100644 --- a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py @@ -1773,6 +1773,10 @@ def test_get_well_position_with_meniscus_and_literal_volume_offset( slot_pos = Point(4, 5, 6) well_def = well_plate_def.wells["B2"] + pip_type = PipetteNameType.P300_SINGLE + decoy.when(mock_pipette_view.get_nozzle_configuration("pipette-id")).then_return( + get_default_nozzle_map(pip_type) + ) decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) decoy.when(mock_labware_view.get_definition("labware-id")).then_return( well_plate_def @@ -1843,7 +1847,10 @@ def test_get_well_position_with_meniscus_and_float_volume_offset( calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) slot_pos = Point(4, 5, 6) well_def = well_plate_def.wells["B2"] - + pip_type = PipetteNameType.P300_SINGLE + decoy.when(mock_pipette_view.get_nozzle_configuration("pipette-id")).then_return( + get_default_nozzle_map(pip_type) + ) decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) decoy.when(mock_labware_view.get_definition("labware-id")).then_return( well_plate_def @@ -1941,6 +1948,10 @@ def test_get_well_position_raises_validation_error( decoy.when(mock_labware_view.get_well_geometry("labware-id", "B2")).then_return( _TEST_INNER_WELL_GEOMETRY ) + pip_type = PipetteNameType.P300_SINGLE + decoy.when(mock_pipette_view.get_nozzle_configuration("pipette-id")).then_return( + get_default_nozzle_map(pip_type) + ) decoy.when( mock_pipette_view.get_current_tip_lld_settings(pipette_id="pipette-id") ).then_return(0.5) @@ -4184,17 +4195,27 @@ def test_virtual_get_well_height_after_liquid_handling_no_error( decoy: Decoy, subject: GeometryView, mock_labware_view: LabwareView, + mock_pipette_view: PipetteView, + well_plate_def: LabwareDefinition, initial_liquid_height: LiquidTrackingType, ) -> None: """Make sure SimulatedLiquidProbe doesn't change geometry behavior.""" + pip_type = PipetteNameType.P300_SINGLE + decoy.when(mock_pipette_view.get_nozzle_configuration("pipette-id")).then_return( + get_default_nozzle_map(pip_type) + ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_well_geometry("labware-id", "B2")).then_return( _TEST_INNER_WELL_GEOMETRY ) operation_volume = 1000.0 - result_estimate = subject.get_well_height_after_liquid_handling_no_error( labware_id="labware-id", well_name="B2", + pipette_id="pipette-id", initial_height=initial_liquid_height, volume=operation_volume, ) @@ -4228,13 +4249,23 @@ def test_virtual_find_height_and_volume( def test_get_liquid_handling_z_change( decoy: Decoy, subject: GeometryView, + well_plate_def: LabwareDefinition, mock_labware_view: LabwareView, + mock_pipette_view: PipetteView, mock_well_view: WellView, ) -> None: """Test for get_liquid_handling_z_change math.""" + pip_type = PipetteNameType.P300_SINGLE + decoy.when(mock_pipette_view.get_nozzle_configuration("pipette-id")).then_return( + get_default_nozzle_map(pip_type) + ) + decoy.when(mock_labware_view.get_well_definition("labware-id", "A1")).then_return( RectangularWellDefinition3.model_construct(totalLiquidVolume=1100000) # type: ignore[call-arg] ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) decoy.when(mock_labware_view.get_well_geometry("labware-id", "A1")).then_return( _TEST_INNER_WELL_GEOMETRY ) @@ -4251,7 +4282,10 @@ def test_get_liquid_handling_z_change( ) # make sure that liquid handling z change math stays the same change = subject.get_liquid_handling_z_change( - labware_id="labware-id", well_name="A1", operation_volume=199.0 + labware_id="labware-id", + well_name="A1", + pipette_id="pipette-id", + operation_volume=199.0, ) expected_change = 3.2968 assert isclose(change, expected_change) diff --git a/api/tests/opentrons/protocol_engine/test_types.py b/api/tests/opentrons/protocol_engine/test_types.py index d48c67ee61ed..fb9a0978bb0e 100644 --- a/api/tests/opentrons/protocol_engine/test_types.py +++ b/api/tests/opentrons/protocol_engine/test_types.py @@ -1,8 +1,14 @@ """Test protocol engine types.""" + import pytest -from pydantic import ValidationError +from pydantic import ValidationError, BaseModel -from opentrons.protocol_engine.types import HexColor +from opentrons.protocol_engine.types import ( + HexColor, + SimulatedProbeResult, + LiquidTrackingType, + WellInfoSummary, +) @pytest.mark.parametrize("hex_color", ["#F00", "#FFCC00CC", "#FC0C", "#98e2d1"]) @@ -20,3 +26,51 @@ def test_handles_invalid_hex(invalid_hex_color: str) -> None: HexColor(invalid_hex_color) with pytest.raises(ValidationError): HexColor.model_validate_json(f'"{invalid_hex_color}"') + + +class _TestModel(BaseModel): + """Test model for deserializing SimulatedProbeResults.""" + + value: LiquidTrackingType + + +def test_roundtrips_simulated_liquid_probe() -> None: + """Should be able to roundtrip our simulated results.""" + base = _TestModel(value=SimulatedProbeResult()) + serialized = base.model_dump_json() + deserialized = _TestModel.model_validate_json(serialized) + assert isinstance(deserialized.value, SimulatedProbeResult) + + +def test_roundtrips_nonsimulated_liquid_probe() -> None: + """Should be able to roundtrip our simulated results.""" + base = _TestModel(value=10.0) + serialized = base.model_dump_json() + deserialized = _TestModel.model_validate_json(serialized) + assert deserialized.value == 10.0 + + +def test_fails_deser_wrong_string() -> None: + """Should fail to deserialize the wrong string.""" + with pytest.raises(ValidationError): + _TestModel.model_validate_json('{"value": "not the right string"}') + + +@pytest.mark.parametrize("height", [None, 10.0, SimulatedProbeResult()]) +def test_roundtrips_well_info_summary(height: LiquidTrackingType | None) -> None: + """It should round trip a WellInfoSummary.""" + inp = WellInfoSummary( + labware_id="hi", + well_name="lo", + loaded_volume=None, + probed_height=height, + probed_volume=height, + ) + outp = WellInfoSummary.model_validate_json(inp.model_dump_json()) + if isinstance(height, SimulatedProbeResult): + assert outp.labware_id == inp.labware_id + assert outp.well_name == inp.well_name + assert isinstance(outp.probed_height, SimulatedProbeResult) + assert isinstance(outp.probed_volume, SimulatedProbeResult) + else: + assert outp == inp diff --git a/app/src/assets/localization/zh/anonymous.json b/app/src/assets/localization/zh/anonymous.json index 0981184ba676..6d555af80d04 100644 --- a/app/src/assets/localization/zh/anonymous.json +++ b/app/src/assets/localization/zh/anonymous.json @@ -40,7 +40,7 @@ "module_error_contact_support": "尝试关闭模块电源,然后再打开。如果报错仍然存在,请与支持人员联系。", "network_setup_menu_description": "您将使用此连接来运行软件更新,并将协议加载到您的工作站上。", "new_robot_instructions": "设置新工作站时,请遵循触摸屏上的指示。有关更多信息,请参阅您的工作站快速入门指南。", - "oem_mode_description": "启用OEM模式,以从Flex触摸屏中移除Opentrons的相关信息。", + "oem_mode_description": "启用 OEM 模式以从 Flex 触摸屏中删除所有 Opentrons 实例。", "opentrons_app_successfully_updated": "应用程序已成功更新。", "opentrons_app_update": "应用程序更新", "opentrons_app_update_available": "应用程序可更新", @@ -65,9 +65,9 @@ "share_app_analytics": "共享应用程序分析数据", "share_app_analytics_description": "通过自动发送匿名诊断和使用数据来帮助改进此产品。", "share_display_usage_description": "关于工作站触摸屏的交互数据。", - "share_logs_with_opentrons": "共享工作站日志", + "share_logs_with_opentrons": "分享机器人日志", "share_logs_with_opentrons_description": "通过自动发送匿名的工作站日志来帮助改进此产品。这些日志用于解决工作站问题和发现错误趋势。", - "show_labware_offset_snippets_description": "仅适用于需要在应用程序之外应用耗材校准数据的用户。启用后,在设置协议过程中可访问Jupyter Notebook和SSH的代码片段。", + "show_labware_offset_snippets_description": "仅适用于需要在应用程序外部应用实验室器具偏移数据的用户。启用后,在配置所有必需的偏移量后,可在协议设置期间使用 Jupyter Notebook 和 SSH 的代码片段。", "something_seems_wrong": "您的移液器可能有问题。退出设置并联系支持人员以获取帮助。", "storage_limit_reached_description": "您的工作站已达到可存储的快速移液数量上限。在创建新的快速移液之前,您必须删除一个现有的快速移液。", "system_language_preferences_update_description": "您系统的语言最近已更新。您想使用更新后的语言作为应用的默认语言吗?", diff --git a/app/src/assets/localization/zh/device_settings.json b/app/src/assets/localization/zh/device_settings.json index 591c9cfcb7ad..cf4b25b02303 100644 --- a/app/src/assets/localization/zh/device_settings.json +++ b/app/src/assets/localization/zh/device_settings.json @@ -50,6 +50,7 @@ "clear_option_deck_calibration": "清除甲板校准", "clear_option_gripper_calibration": "清除转板抓手校准", "clear_option_gripper_offset_calibrations": "清除转板抓手校准", + "clear_option_labware_offsets": "清除耗材校准数据", "clear_option_module_calibration": "清除模块校准", "clear_option_pipette_calibrations": "清除移液器校准", "clear_option_pipette_offset_calibrations": "清除移液器偏移校准", @@ -231,6 +232,7 @@ "privacy": "隐私", "problem_during_update": "此次更新耗时比平常要长。", "proceed_without_updating": "跳过更新以继续", + "protocol_run_data": "协议运行数据", "recalibrate_deck": "重新校准甲板", "recalibrate_gripper": "重新校准转板抓手", "recalibrate_module": "重新校准模块", diff --git a/app/src/assets/localization/zh/error_recovery.json b/app/src/assets/localization/zh/error_recovery.json index a740eac779dd..ca9d718c2379 100644 --- a/app/src/assets/localization/zh/error_recovery.json +++ b/app/src/assets/localization/zh/error_recovery.json @@ -21,23 +21,24 @@ "continue_to_drop_tip": "继续丢弃吸头", "do_you_need_to_blowout": "首先,请问需要排出枪头内的液体吗?", "door_open_robot_home": "在手动移动实验用品前,设备需要安全归位。", + "droplets_or_liquid_cause_failure": "吸头内的液滴或液体可能会导致液位检测失败", "ensure_lw_is_accurately_placed": "确保实验耗材已准确放置在甲板槽中,防止进一步出现错误", "error": "错误", "error_details": "错误详情", "error_on_robot": "{{robot}}上的错误", "failed_dispense_step_not_completed": "中断运行的最后一步液体排出失败,恢复程序将不会继续运行这一步骤,请手动完成这一步的移液操作。运行将继续从下一步开始。继续之前,请关闭工作站门。", "failed_step": "失败步骤", - "first_is_gripper_holding_labware": "首先,抓扳手是否夹着实验耗材?", + "first_is_gripper_holding_labware": "首先,转板抓手是否夹着实验耗材?", "go_back": "返回", - "gripper_error": "抓扳手错误", - "gripper_errors_occur_when": "当抓扳手停滞或与甲板上另一物体碰撞时,会发生抓扳手错误,这通常是由于实验耗材放置不当或实验耗材偏移不准确所致", - "gripper_releasing_labware": "抓扳手正在释放实验耗材", - "gripper_will_release_in_s": "抓扳手将在{{seconds}}秒后释放实验耗材", + "gripper_error": "转板抓手错误", + "gripper_errors_occur_when": "当转板抓手停滞或与甲板上另一物体碰撞时,会发生转板抓手错误,这通常是由于实验耗材放置不当或实验耗材偏移不准确所致", + "gripper_releasing_labware": "转板抓手正在释放实验耗材", + "gripper_will_release_in_s": "转板抓手将在{{seconds}}秒后释放实验耗材", "home_and_retry": "归位并重试该步骤", "home_gantry": "归位", "home_now": "现在归位", "homing_pipette_dangerous": "如果移液器中有液体,将{{mount}}移液器归位可能会损坏它。您必须在使用移液器之前取下所有吸头。", - "if_issue_persists_gripper_error": "如果问题持续存在,请取消运行并重新运行抓扳手校准", + "if_issue_persists_gripper_error": "如果问题持续存在,请取消运行并重新运行转板抓手校准", "if_issue_persists_overpressure": "如果问题持续存在,请取消运行并对协议进行必要的更改", "if_issue_persists_tip_not_detected": "如果问题持续存在,请取消运行并启动实验耗材位置检查", "if_tips_are_attached": "如果吸头还在移液器上,您可以在运行终止前选择吹出已吸取的液体并丢弃吸头。", @@ -49,7 +50,8 @@ "labware_released_from_current_height": "将从当前高度释放实验耗材", "launch_recovery_mode": "启动恢复模式", "manually_fill_liquid_in_well": "手动填充孔位{{well}}中的液体", - "manually_fill_well_and_skip": "手动填充孔位并跳到下一步", + "manually_fill_well_and_retry_new_tips": "手动填充好并用新吸头重试", + "manually_fill_well_and_retry_same_tips": "手动填充并用相同的提示重试", "manually_move_lw_and_skip": "手动移动实验耗材并跳至下一步", "manually_move_lw_on_deck": "手动移动实验耗材", "manually_replace_lw_and_retry": "手动更换实验耗材并重试此步骤", @@ -109,6 +111,7 @@ "stand_back_resuming": "请远离,正在恢复当前步骤", "stand_back_retrying": "请远离,正在重试失败步骤", "stand_back_skipping_to_next_step": "请远离,正在跳到下一步骤", + "static_meniscus_less_accurate": "如果使用静态弯月面移液,跳过液体存在检测时液体跟踪可能不太准确。", "take_any_necessary_precautions": "在接住实验耗材之前,请采取必要的预防措施。确认后,夹爪将开始倒计时再释放。", "take_necessary_actions": "首先,采取任何必要的行动,让工作站重新尝试失败的步骤。然后,在继续之前关闭工作站门。", "take_necessary_actions_failed_pickup": "首先,采取任何必要的行动,让工作站重新尝试移液器拾取。然后,在继续之前关闭工作站门。", @@ -119,6 +122,7 @@ "tip_drop_failed": "丢弃吸头失败", "tip_not_detected": "未检测到吸头", "tip_presence_errors_are_caused": "吸头识别错误通常是由实验器皿放置不当或器皿偏移不准确引起的。", + "use_dry_unused_tips": "使用干燥、未使用过的吸头以获得最佳效果", "view_error_details": "查看错误详情", "view_recovery_options": "查看恢复选项", "you_can_still_drop_tips": "在继续选择吸头之前,您仍然可以丢弃移液器上现存的吸头。" diff --git a/app/src/assets/localization/zh/labware_position_check.json b/app/src/assets/localization/zh/labware_position_check.json index 3c66cff922dc..3ecbec490626 100644 --- a/app/src/assets/localization/zh/labware_position_check.json +++ b/app/src/assets/localization/zh/labware_position_check.json @@ -2,41 +2,83 @@ "adapter_in_mod_in_slot": "{{slot}}中{{module}}上的{{adapter}}", "adapter_in_slot": "{{slot}}上的{{adapter}}", "adapter_in_tc": "{{module}}上的{{adapter}}", + "add": "添加", + "add_a_default_offset": "添加耗材默认校准数据,并自动应用于甲板上所有位置", + "add_default_labware_offset": "添加耗材默认校准数据", + "adjust": "调整", + "adjust_applied_location_offset": "调整已应用的校准数据", + "adjust_default_labware_offset": "调整默认耗材校准数据", + "align_to_top_of_labware": "与耗材顶部对齐", "all_modules_and_labware_from_protocol": "{{protocol_name}}协议中使用的所有模块与耗材 ", + "applied_location_offset_adjusted": "已调整位置校准数据", + "applied_location_offsets": "已应用的位置校准数据", "applied_offset_data": "已应用的耗材校准数据", "apply_offset_data": "应用耗材校准数据", "apply_offsets": "应用校准数据", "attach_probe": "安装校准探头", "backmost": "最靠后的", "calibration_probe": "校准探头", + "calibration_probe_not_detected": "未检测到校准探头", + "cancel": "取消", + "changing_default_not_update_hardcoded": "更改默认校准数据不会自动更新代码内的校准数据", "check_item_in_location": "检查{{location}}上的{{item}}", "check_labware_in_slot_title": "检查板位{{slot}}中的耗材{{labware_display_name}}", "check_remaining_labware_with_primary_pipette_section": "使用{{primary_mount}}移液器和吸头检查剩余耗材", "check_tip_location": "A1的吸头尖端", "check_well_location": "耗材上的A1孔", + "clear_deck_all_lw_all_modules_from": "清除所有甲板位上的耗材和 {{slot}}上的所有模块", + "clear_deck_all_lw_leave_modules": "轻触所有甲板位上的耗材,保留模块不动", "cli_ssh": "命令行界面 (SSH)", "close_and_apply_offset_data": "关闭并保存耗材校准数据", + "complete": "完成", + "confirm": "确认", "confirm_detached": "确认移除", + "confirm_go_back_without_saving": "请问您确定要不保存并返回耗材列表吗?", "confirm_pick_up_tip_modal_title": "移液器是否成功拾取了吸头?", "confirm_pick_up_tip_modal_try_again_text": "否,重试", + "confirm_placement": "确认放置", "confirm_position_and_move": "确认位置,移动到板位{{next_slot}}", "confirm_position_and_pick_up_tip": "确认位置,取吸头", "confirm_position_and_return_tip": "确认位置,将吸头返回至板位{{next_slot}}并复位", + "confirm_removal": "确认移除", + "continue": "继续", + "default": "默认", + "default_labware_offset": "默认耗材校准数据", + "default_location_offset_added": "添加了默认耗材校准数据", + "default_location_offset_adjusted": "已调整默认耗材校准数据", + "default_offset_description": "除非您调整校准数据,否则放置的耗材都将使用默认数据。", "detach_probe": "移除校准探头", "ensure_nozzle_position_desktop": "请检查并确认{{tip_type}}位于{{item_location}}正上方并水平对齐。如果位置不正确,请使用下方的控制按键或键盘微调移液器直至完全对齐。", "ensure_nozzle_position_odd": "请检查并确认{{tip_type}}位于{{item_location}}正上方并水平对齐。如果位置不正确,请点击移动移液器,然后微调移液器直至完全对齐。", + "ensure_probe_attached": "继续操作之前请确保其已正确连接。", + "ensure_probe_position_desktop": "确保校准探头居中并与 {{item_location}}齐平。否则,请使用下面的操作台或键盘来移动移液器,直到其正确对齐。", + "ensure_probe_position_odd": "确保校准探头居中并与 {{item_location}}齐平。否则,请点击 移动移液器 然后调整移液器与其正确对齐。", + "ensure_tip_rack_accurately_placed": "确保吸头盒按照上述步骤准确放置在甲板位中,防止损坏耗材。", + "exit": "退出", "exit_screen_confirm_exit": "不保存耗材校准数据并退出", "exit_screen_go_back": "返回耗材位置校准", "exit_screen_subtitle": "如果您现在退出,所有耗材校准数据都将不保留,且无法恢复。", "exit_screen_title": "确定要在完成耗材位置校准前退出?", + "failed_to_save_final_position": "保存最终位置失败", "get_labware_offset_data": "获取耗材校准数据", + "go_back": "返回", + "go_back_confirmation": "您确定要不保存并返回耗材列表吗?", + "hardcoded": "代码", + "hardcoded_offsets_changed_in_python": "必须在 Python 协议中更改代码耗材校准数据", + "install_probe_1ch": "将校准探针从存放位置取出,确保套环已完全解锁。将探针牢牢地安装到移液器喷嘴上,直至完全卡住。旋转套环,锁定探针。轻触探头,确认已完全固定。探头应该绑定牢固。", + "install_probe_8ch": "将校准探头从存放位置取出,确保套环完全解锁。将探头安装到 最后面 移液器喷嘴处,往上尽可能地压紧。旋转套环,锁定探头。轻触探头,确认已完全固定。探头应该绑定牢固。", + "install_probe_96ch": "将校准探头从存放位置取出,确保套环完全解锁。将探头安装到 A1(左后角) 移液器喷嘴处,往上尽可能地压紧。旋转套环,锁定探头。轻触探头,确认已完全固定。探头应该绑定牢固。", "jog_controls_adjustment": "需要进行调整吗?", + "jog_too_far": "移动过远?", "jupyter_notebook": "Jupyter Notebook", "labware": "耗材", + "labware_default_offset_added": "添加了默认校准数据{{labware}} ", + "labware_default_offset_updated": "{{labware}} 默认校准数据已更新", "labware_display_location_text": "甲板板位{{slot}}", "labware_offset": "耗材校准数据", "labware_offset_data": "耗材校准数据", "labware_offsets_deleted_warning": "一旦开始耗材位置校准,之前创建的耗材校准数据将会丢失。", + "labware_offsets_saved": "{{labware}} 校准数据已保存", "labware_offsets_summary_labware": "耗材", "labware_offsets_summary_location": "位置", "labware_offsets_summary_offset": "耗材校准数据", @@ -59,25 +101,49 @@ "labware_step_detail_modal_nozzle_or_tip_image_3_text": "如遇觉得难以判断,请在吸嘴与吸头之间放入一张常规纸张。当这张纸能刚好卡在两者之间时,可确认高度位置。", "labware_step_detail_tiprack": "移液器吸嘴应居中于{{tiprack_name}}的A1位置上方,并且与吸头顶部水平对齐。", "labware_step_detail_tiprack_plural": "移液器吸嘴应位于{{tiprack_name}}第一列正上方并居中对齐,并且与吸头顶部水平对齐。", + "labware_type": "耗材类型", "learn_more": "了解更多", + "legacy_calibration_probe": "校准探头", + "legacy_clear_all_slots": "清除所有甲板位上的耗材,保留模块在原位", + "legacy_clear_all_slots_odd": "清除所有甲板位上的耗材", + "legacy_install_probe": "将校准探头从存放位置取出,确保套环已完全解锁。将探头安装到 {{location}} 移液器喷嘴处,往上尽可能地压紧。旋转套环,锁定探头。轻触探头,确认已完全固定。", + "legacy_labware_offset_data": "耗材校准数据", + "legacy_labware_position_check_description": "耗材校准是一种引导式工作流程,用于校准甲板位上的耗材,以提高协议运行的精确度。耗材校准首先校准吸头架位置,然后校准协议中使用的所有其他耗材。", + "legacy_no_offset_data": "无可用校准数据", "location": "位置", + "location_header": "位置", + "lpc_complete": "耗材校准完成", "lpc_complete_summary_screen_heading": "耗材位置校准完成", + "manual": "手动地", + "modify_hardcoded_offsets_in_protocol": "代码校准数据必须要在您的Python协议文件中进行修改", "module_display_location_text": "{{moduleName}}位于甲板板位{{slot}}", "module_in_slot": "{{module}}位于板位{{slot}}", + "move_gantry_to_front": "将龙门架移至前端", "move_pipette": "移动移液器", "move_to_a1_position": "将移液器移到A1位置并对齐", "moving_to_slot_title": "正在移动到板位{{slot}}", + "need_help": "请问需要帮助吗?", "new_labware_offset_data": "新的耗材校准数据", + "next_place_a_full_tip_rack_in_location": "接下来,放置 完整的 {{tip_rack}}{{location}}", + "next_place_labware_in_location": "接下来,放置 {{labware}}{{location}}", "ninety_six_probe_location": "A1(左上角)", "no_labware_offsets": "无耗材校准数据", "no_offset_data": "没有可用的校准数据", "no_offset_data_available": "没有可用的耗材校准数据", "no_offset_data_on_robot": "这轮运行中此工作站没有可用的耗材校准数据。", + "not_applicable": "N/A", + "num_missing_offsets": "{{num}} 缺失校准数据", + "num_offsets": "{{num}} 校准数据", + "offset_values": "X {{x}},Y {{y}},Z {{z}}", "offsets": "校准数据", + "offsets_already_applied": "耗材校准数据已应用", + "one_missing_offset": "缺少 1 个校准数据", + "one_offset": "1 个校准数据", "pick_up_tip_from_rack_in_location": "从位于{{location}}的吸头盒上拾取吸头", "picking_up_tip_title": "在板位{{slot}}拾取吸头", "pipette_nozzle": "离您最远的移液器吸嘴", "place_a_full_tip_rack_in_location": "将装满吸头的{{tip_rack}}放入{{location}}", + "place_a_full_tip_rack_in_location_96ch_default": "放置完整的 {{tip_rack}}{{location}} ,不要放置吸头架适配器", "place_labware_in_adapter_in_location": "在{{location}}先放置{{adapter}},再放置{{labware}}", "place_labware_in_location": "将{{labware}}放入{{location}}", "place_modules": "在甲板上放置模块", @@ -88,6 +154,8 @@ "remove_calibration_probe": "移除校准探头", "remove_probe": "将校准探头解锁,将其从吸嘴上拆下,并放回存储位置。", "remove_probe_before_exit": "退出前请移除校准探头", + "remove_probe_before_exiting_error": "退出前,请先移除校准探头。然后,重新启动耗材校准,然后继续。", + "reset_to_default": "恢复默认设置", "return_tip_rack_to_location": "将吸头盒放回{{location}}", "return_tip_section": "放回吸头", "returning_tip_title": "正在板位{{slot}}放回吸头", @@ -96,17 +164,30 @@ "robot_has_offsets_from_previous_runs": "此工作站具有本协议中所用耗材的校准数据。如果您应用了这些校准数据,仍可通过耗材位置校准程序进行调整。", "robot_in_motion": "工作站正在运行,请远离。", "run": "运行", + "save": "保存", "secondary_pipette_tipracks_section": "使用{{secondary_mount}}移液器检查吸头盒", "see_how_offsets_work": "了解耗材校准的工作原理", + "select_labware_to_view_data": "选择一个耗材来查看其存储的校准数据。", + "skip": "跳过", "slot": "板位{{slotName}}", + "slot_applied_location_offset_updated": "{{slot}} 的位置数据已更新", + "slot_in_module_applied_location_offset_updated": "在{{slot}} 上的 {{module}} 位置校准数据已更新", "slot_location": "板位位置", "slot_name": "板位{{slotName}}", + "something_went_wrong": "出了点问题", + "specific_slots_can_be_adjusted": "可以根据需要调整特定的甲板位置", + "start_over": "重新开始", "start_position_check": "开始耗材位置校准程序,移至板位{{initial_labware_slot}}", + "store_probe": "退出之前,请解锁校准探头,将其从移液器下方取出,然后将其放回存放位置。", "stored_offset_data": "应用已存储的耗材校准数据?", "stored_offsets_for_this_protocol": "适用于本协议的已存储耗材校准数据", "table_view": "表格视图", "tip_rack": "吸头盒", + "total_offsets": "全部校准数据", + "try_again": "再次尝试", + "unsaved_changes_will_be_lost": "未保存的更改将会丢失", "view_current_offsets": "查看当前校准数据", "view_data": "查看数据", + "view_labware_list": "查看耗材列表", "what_is_labware_offset_data": "什么是耗材校准数据?" } diff --git a/app/src/assets/localization/zh/protocol_setup.json b/app/src/assets/localization/zh/protocol_setup.json index bd936d8540a1..47383f748bf6 100644 --- a/app/src/assets/localization/zh/protocol_setup.json +++ b/app/src/assets/localization/zh/protocol_setup.json @@ -82,7 +82,6 @@ "deck_conflict_info": "通过移除位置 {{cutout}} 中的 {{currentFixture}} 来更新甲板配置。从甲板配置中移除对应装置或更新协议。", "deck_conflict_info_thermocycler": "通过移除位置 A1 和 B1 中的固定装置来更新甲板配置。从甲板配置中移除对应装置或更新协议。", "deck_hardware": "甲板硬件", - "deck_hardware_ready": "甲板硬件准备", "deck_map": "甲板布局图", "default_values": "默认值", "download_files": "下载文件", @@ -112,6 +111,7 @@ "instrument_calibrations_missing": "缺少{{count}}个校准", "instrument_calibrations_missing_plural": "缺少{{count}}个校准", "instruments": "硬件", + "instruments_attached": "已附仪器", "instruments_connected": "已连接{{count}}个硬件", "instruments_connected_plural": "已连接{{count}}个硬件", "jupyter_notebook": "Jupyter Notebook", @@ -183,8 +183,10 @@ "module_setup_step_title": "甲板硬件", "module_slot_location": "{{slotName}}号板位,{{moduleName}}", "modules": "模块", + "modules_and_fixtures_ready": "模块和固定装置已准备就绪", "modules_connected": "连接了{{count}}个模块", "modules_connected_plural": "连接了{{count}}个模块", + "modules_ready": "模块已准备就绪", "modules_setup_step_title": "模块设置", "mount": "{{mount}}安装支架", "mount_title": "{{mount}}安装支架:", @@ -240,6 +242,7 @@ "on_adapter": "在{{adapterName}}上", "on_adapter_in_mod": "在{{moduleName}}中的{{adapterName}}上", "on_deck": "在甲板上", + "one_missing_offset": "缺少 1 个校准数据", "opening": "打开中...", "parameters": "参数", "pipette_mismatch": "移液器型号不匹配。", diff --git a/app/src/redux/analytics/constants.ts b/app/src/redux/analytics/constants.ts index 4d986162b31f..945d37c4681f 100644 --- a/app/src/redux/analytics/constants.ts +++ b/app/src/redux/analytics/constants.ts @@ -120,8 +120,7 @@ export const ANALYTICS_LANGUAGE_UPDATED_DESKTOP_APP_SETTINGS: 'languageUpdatedDe * LPC Analytics */ -export const ANALYTICS_LPC_ANALYSIS_KIND: 'analytics:lpcAnalysisKind' = - 'analytics:lpcAnalysisKind' +export const ANALYTICS_LPC_ANALYSIS_KIND: 'lpcAnalysisKind' = 'lpcAnalysisKind' /** * Module Actions Analytics diff --git a/shared-data/labware/definitions/2/ev_resin_tips_flex_96_tiprack_adapter/1.json b/shared-data/labware/definitions/2/ev_resin_tips_flex_96_tiprack_adapter/1.json index 25d2e12b366e..edbb53416762 100644 --- a/shared-data/labware/definitions/2/ev_resin_tips_flex_96_tiprack_adapter/1.json +++ b/shared-data/labware/definitions/2/ev_resin_tips_flex_96_tiprack_adapter/1.json @@ -8,7 +8,7 @@ ] }, "metadata": { - "displayName": "Opentrons Flex EV Resin Tips Tall Adapter for Third-party Evotips in Flex 96 Tip Rack Adapter", + "displayName": "Opentrons Flex EV Resin Tips Tall Adapter for Third-party Evotips in 96 Tip Rack Adapter", "displayCategory": "adapter", "displayVolumeUnits": "µL", "tags": []