opentrons 8.2.0a4__py2.py3-none-any.whl → 8.3.0a1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opentrons/calibration_storage/deck_configuration.py +3 -3
- opentrons/calibration_storage/file_operators.py +3 -3
- opentrons/calibration_storage/helpers.py +3 -1
- opentrons/calibration_storage/ot2/models/v1.py +16 -29
- opentrons/calibration_storage/ot2/tip_length.py +7 -4
- opentrons/calibration_storage/ot3/models/v1.py +14 -23
- opentrons/cli/analyze.py +18 -6
- opentrons/config/defaults_ot3.py +1 -0
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/errors.py +16 -3
- opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
- opentrons/drivers/command_builder.py +2 -2
- opentrons/drivers/flex_stacker/__init__.py +9 -0
- opentrons/drivers/flex_stacker/abstract.py +89 -0
- opentrons/drivers/flex_stacker/driver.py +260 -0
- opentrons/drivers/flex_stacker/simulator.py +109 -0
- opentrons/drivers/flex_stacker/types.py +138 -0
- opentrons/drivers/heater_shaker/driver.py +18 -3
- opentrons/drivers/temp_deck/driver.py +13 -3
- opentrons/drivers/thermocycler/driver.py +17 -3
- opentrons/execute.py +3 -1
- opentrons/hardware_control/__init__.py +1 -2
- opentrons/hardware_control/api.py +28 -20
- opentrons/hardware_control/backends/flex_protocol.py +17 -7
- opentrons/hardware_control/backends/ot3controller.py +213 -63
- opentrons/hardware_control/backends/ot3simulator.py +18 -9
- opentrons/hardware_control/backends/ot3utils.py +43 -15
- opentrons/hardware_control/dev_types.py +4 -0
- opentrons/hardware_control/emulation/heater_shaker.py +4 -0
- opentrons/hardware_control/emulation/module_server/client.py +1 -1
- opentrons/hardware_control/emulation/module_server/server.py +5 -3
- opentrons/hardware_control/emulation/settings.py +3 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
- opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
- opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
- opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
- opentrons/hardware_control/modules/mod_abc.py +2 -2
- opentrons/hardware_control/motion_utilities.py +68 -0
- opentrons/hardware_control/nozzle_manager.py +39 -41
- opentrons/hardware_control/ot3_calibration.py +1 -1
- opentrons/hardware_control/ot3api.py +60 -23
- opentrons/hardware_control/protocols/gripper_controller.py +3 -0
- opentrons/hardware_control/protocols/hardware_manager.py +5 -1
- opentrons/hardware_control/protocols/liquid_handler.py +18 -0
- opentrons/hardware_control/protocols/motion_controller.py +6 -0
- opentrons/hardware_control/robot_calibration.py +1 -1
- opentrons/hardware_control/types.py +61 -0
- opentrons/protocol_api/__init__.py +20 -1
- opentrons/protocol_api/_liquid.py +24 -49
- opentrons/protocol_api/_liquid_properties.py +754 -0
- opentrons/protocol_api/_types.py +24 -0
- opentrons/protocol_api/core/common.py +2 -0
- opentrons/protocol_api/core/engine/instrument.py +82 -10
- opentrons/protocol_api/core/engine/labware.py +29 -7
- opentrons/protocol_api/core/engine/protocol.py +130 -5
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/well.py +4 -1
- opentrons/protocol_api/core/instrument.py +46 -4
- opentrons/protocol_api/core/labware.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +37 -3
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +37 -3
- opentrons/protocol_api/core/protocol.py +34 -1
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/instrument_context.py +158 -44
- opentrons/protocol_api/labware.py +231 -7
- opentrons/protocol_api/module_contexts.py +21 -17
- opentrons/protocol_api/protocol_context.py +125 -4
- opentrons/protocol_api/robot_context.py +204 -32
- opentrons/protocol_api/validation.py +262 -3
- opentrons/protocol_engine/__init__.py +4 -0
- opentrons/protocol_engine/actions/actions.py +2 -3
- opentrons/protocol_engine/clients/sync_client.py +18 -0
- opentrons/protocol_engine/commands/__init__.py +81 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
- opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
- opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
- opentrons/protocol_engine/commands/aspirate.py +103 -53
- opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
- opentrons/protocol_engine/commands/blow_out.py +44 -39
- opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
- opentrons/protocol_engine/commands/command.py +73 -66
- opentrons/protocol_engine/commands/command_unions.py +101 -1
- opentrons/protocol_engine/commands/comment.py +1 -1
- opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
- opentrons/protocol_engine/commands/custom.py +6 -12
- opentrons/protocol_engine/commands/dispense.py +82 -48
- opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
- opentrons/protocol_engine/commands/drop_tip.py +52 -31
- opentrons/protocol_engine/commands/drop_tip_in_place.py +13 -3
- opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/home.py +13 -4
- opentrons/protocol_engine/commands/liquid_probe.py +67 -24
- opentrons/protocol_engine/commands/load_labware.py +29 -7
- opentrons/protocol_engine/commands/load_lid.py +146 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
- opentrons/protocol_engine/commands/load_liquid.py +12 -4
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +31 -10
- opentrons/protocol_engine/commands/load_pipette.py +19 -8
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
- opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
- opentrons/protocol_engine/commands/move_labware.py +19 -6
- opentrons/protocol_engine/commands/move_relative.py +35 -25
- opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
- opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
- opentrons/protocol_engine/commands/move_to_well.py +40 -24
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
- opentrons/protocol_engine/commands/pipetting_common.py +169 -87
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
- opentrons/protocol_engine/commands/reload_labware.py +1 -1
- opentrons/protocol_engine/commands/retract_axis.py +1 -1
- opentrons/protocol_engine/commands/robot/__init__.py +69 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
- opentrons/protocol_engine/commands/robot/common.py +18 -0
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
- opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
- opentrons/protocol_engine/commands/robot/move_to.py +94 -0
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
- opentrons/protocol_engine/commands/save_position.py +14 -5
- opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
- opentrons/protocol_engine/commands/set_status_bar.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +8 -2
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/touch_tip.py +65 -16
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
- opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
- opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
- opentrons/protocol_engine/errors/__init__.py +8 -0
- opentrons/protocol_engine/errors/error_occurrence.py +19 -20
- opentrons/protocol_engine/errors/exceptions.py +50 -0
- opentrons/protocol_engine/execution/command_executor.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +73 -5
- opentrons/protocol_engine/execution/gantry_mover.py +364 -8
- opentrons/protocol_engine/execution/movement.py +27 -0
- opentrons/protocol_engine/execution/pipetting.py +5 -1
- opentrons/protocol_engine/execution/tip_handler.py +4 -6
- opentrons/protocol_engine/notes/notes.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +7 -6
- opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_validation.py +5 -0
- opentrons/protocol_engine/resources/module_data_provider.py +1 -1
- opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
- opentrons/protocol_engine/slot_standardization.py +9 -9
- opentrons/protocol_engine/state/_move_types.py +9 -5
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +25 -61
- opentrons/protocol_engine/state/command_history.py +12 -0
- opentrons/protocol_engine/state/commands.py +17 -13
- opentrons/protocol_engine/state/files.py +10 -12
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/frustum_helpers.py +57 -32
- opentrons/protocol_engine/state/geometry.py +47 -1
- opentrons/protocol_engine/state/labware.py +79 -25
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +16 -4
- opentrons/protocol_engine/state/modules.py +52 -70
- opentrons/protocol_engine/state/motion.py +6 -1
- opentrons/protocol_engine/state/pipettes.py +144 -58
- opentrons/protocol_engine/state/state.py +21 -2
- opentrons/protocol_engine/state/state_summary.py +4 -2
- opentrons/protocol_engine/state/tips.py +11 -44
- opentrons/protocol_engine/state/update_types.py +343 -48
- opentrons/protocol_engine/state/wells.py +19 -11
- opentrons/protocol_engine/types.py +176 -28
- opentrons/protocol_reader/extract_labware_definitions.py +5 -2
- opentrons/protocol_reader/file_format_validator.py +5 -5
- opentrons/protocol_runner/json_file_reader.py +9 -3
- opentrons/protocol_runner/json_translator.py +51 -25
- opentrons/protocol_runner/legacy_command_mapper.py +66 -64
- opentrons/protocol_runner/protocol_runner.py +35 -4
- opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
- opentrons/protocol_runner/run_orchestrator.py +13 -3
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +1 -1
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +56 -0
- opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +1 -1
- opentrons/protocols/api_support/util.py +10 -0
- opentrons/protocols/labware.py +39 -6
- opentrons/protocols/models/json_protocol.py +5 -9
- opentrons/simulate.py +3 -1
- opentrons/types.py +162 -2
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/METADATA +16 -15
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/RECORD +229 -202
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/WHEEL +1 -1
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/entry_points.txt +0 -0
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Equipment command side-effect logic."""
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from typing import Optional, overload, Union
|
|
3
|
+
from typing import Optional, overload, Union, List
|
|
4
4
|
|
|
5
5
|
from opentrons_shared_data.pipette.types import PipetteNameType
|
|
6
6
|
|
|
@@ -152,10 +152,6 @@ class EquipmentHandler:
|
|
|
152
152
|
Returns:
|
|
153
153
|
A LoadedLabwareData object.
|
|
154
154
|
"""
|
|
155
|
-
labware_id = (
|
|
156
|
-
labware_id if labware_id is not None else self._model_utils.generate_id()
|
|
157
|
-
)
|
|
158
|
-
|
|
159
155
|
definition_uri = uri_from_details(
|
|
160
156
|
load_name=load_name,
|
|
161
157
|
namespace=namespace,
|
|
@@ -172,6 +168,10 @@ class EquipmentHandler:
|
|
|
172
168
|
version=version,
|
|
173
169
|
)
|
|
174
170
|
|
|
171
|
+
labware_id = (
|
|
172
|
+
labware_id if labware_id is not None else self._model_utils.generate_id()
|
|
173
|
+
)
|
|
174
|
+
|
|
175
175
|
# Allow propagation of ModuleNotLoadedError.
|
|
176
176
|
offset_id = self.find_applicable_labware_offset_id(
|
|
177
177
|
labware_definition_uri=definition_uri,
|
|
@@ -379,6 +379,74 @@ class EquipmentHandler:
|
|
|
379
379
|
definition=attached_module.definition,
|
|
380
380
|
)
|
|
381
381
|
|
|
382
|
+
async def load_lids(
|
|
383
|
+
self,
|
|
384
|
+
load_name: str,
|
|
385
|
+
namespace: str,
|
|
386
|
+
version: int,
|
|
387
|
+
location: LabwareLocation,
|
|
388
|
+
quantity: int,
|
|
389
|
+
) -> List[LoadedLabwareData]:
|
|
390
|
+
"""Load one or many lid labware by assigning an identifier and pulling required data.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
load_name: The lid labware's load name.
|
|
394
|
+
namespace: The lid labware's namespace.
|
|
395
|
+
version: The lid labware's version.
|
|
396
|
+
location: The deck location at which lid(s) will be placed.
|
|
397
|
+
labware_ids: An optional list of identifiers to assign the labware. If None,
|
|
398
|
+
an identifier will be generated.
|
|
399
|
+
|
|
400
|
+
Raises:
|
|
401
|
+
ModuleNotLoadedError: If `location` references a module ID
|
|
402
|
+
that doesn't point to a valid loaded module.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
A list of LoadedLabwareData objects.
|
|
406
|
+
"""
|
|
407
|
+
definition_uri = uri_from_details(
|
|
408
|
+
load_name=load_name,
|
|
409
|
+
namespace=namespace,
|
|
410
|
+
version=version,
|
|
411
|
+
)
|
|
412
|
+
try:
|
|
413
|
+
# Try to use existing definition in state.
|
|
414
|
+
definition = self._state_store.labware.get_definition_by_uri(definition_uri)
|
|
415
|
+
except LabwareDefinitionDoesNotExistError:
|
|
416
|
+
definition = await self._labware_data_provider.get_labware_definition(
|
|
417
|
+
load_name=load_name,
|
|
418
|
+
namespace=namespace,
|
|
419
|
+
version=version,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
stack_limit = definition.stackLimit if definition.stackLimit is not None else 1
|
|
423
|
+
if quantity > stack_limit:
|
|
424
|
+
raise ValueError(
|
|
425
|
+
f"Requested quantity {quantity} is greater than the stack limit of {stack_limit} provided by definition for {load_name}."
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Allow propagation of ModuleNotLoadedError.
|
|
429
|
+
if (
|
|
430
|
+
isinstance(location, DeckSlotLocation)
|
|
431
|
+
and definition.parameters.isDeckSlotCompatible is not None
|
|
432
|
+
and not definition.parameters.isDeckSlotCompatible
|
|
433
|
+
):
|
|
434
|
+
raise ValueError(
|
|
435
|
+
f"Lid Labware {load_name} cannot be loaded onto a Deck Slot."
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
load_labware_data_list = []
|
|
439
|
+
for i in range(quantity):
|
|
440
|
+
load_labware_data_list.append(
|
|
441
|
+
LoadedLabwareData(
|
|
442
|
+
labware_id=self._model_utils.generate_id(),
|
|
443
|
+
definition=definition,
|
|
444
|
+
offsetId=None,
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
return load_labware_data_list
|
|
449
|
+
|
|
382
450
|
async def configure_for_volume(
|
|
383
451
|
self, pipette_id: str, volume: float, tip_overlap_version: Optional[str]
|
|
384
452
|
) -> LoadedConfigureForVolumeData:
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
"""Gantry movement wrapper for hardware and simulation based movement."""
|
|
2
|
-
from
|
|
2
|
+
from logging import getLogger
|
|
3
|
+
from opentrons.config.types import OT3Config
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import Optional, List, Dict, Tuple
|
|
3
6
|
from typing_extensions import Protocol as TypingProtocol
|
|
4
7
|
|
|
5
|
-
from opentrons.types import Point, Mount
|
|
8
|
+
from opentrons.types import Point, Mount, MountType
|
|
6
9
|
|
|
7
10
|
from opentrons.hardware_control import HardwareControlAPI
|
|
8
|
-
from opentrons.hardware_control.types import Axis as HardwareAxis
|
|
11
|
+
from opentrons.hardware_control.types import Axis as HardwareAxis, CriticalPoint
|
|
12
|
+
from opentrons.hardware_control.motion_utilities import (
|
|
13
|
+
target_axis_map_from_relative,
|
|
14
|
+
target_axis_map_from_absolute,
|
|
15
|
+
)
|
|
9
16
|
from opentrons_shared_data.errors.exceptions import PositionUnknownError
|
|
10
17
|
|
|
11
18
|
from opentrons.motion_planning import Waypoint
|
|
@@ -14,6 +21,8 @@ from ..state.state import StateView
|
|
|
14
21
|
from ..types import MotorAxis, CurrentWell
|
|
15
22
|
from ..errors import MustHomeError, InvalidAxisForRobotType
|
|
16
23
|
|
|
24
|
+
log = getLogger(__name__)
|
|
25
|
+
|
|
17
26
|
|
|
18
27
|
_MOTOR_AXIS_TO_HARDWARE_AXIS: Dict[MotorAxis, HardwareAxis] = {
|
|
19
28
|
MotorAxis.X: HardwareAxis.X,
|
|
@@ -24,8 +33,38 @@ _MOTOR_AXIS_TO_HARDWARE_AXIS: Dict[MotorAxis, HardwareAxis] = {
|
|
|
24
33
|
MotorAxis.RIGHT_PLUNGER: HardwareAxis.C,
|
|
25
34
|
MotorAxis.EXTENSION_Z: HardwareAxis.Z_G,
|
|
26
35
|
MotorAxis.EXTENSION_JAW: HardwareAxis.G,
|
|
36
|
+
MotorAxis.AXIS_96_CHANNEL_CAM: HardwareAxis.Q,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_MOTOR_AXIS_TO_HARDWARE_MOUNT: Dict[MotorAxis, Mount] = {
|
|
40
|
+
MotorAxis.LEFT_Z: Mount.LEFT,
|
|
41
|
+
MotorAxis.RIGHT_Z: Mount.RIGHT,
|
|
42
|
+
MotorAxis.EXTENSION_Z: Mount.EXTENSION,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_HARDWARE_MOUNT_MOTOR_AXIS_TO: Dict[Mount, MotorAxis] = {
|
|
46
|
+
Mount.LEFT: MotorAxis.LEFT_Z,
|
|
47
|
+
Mount.RIGHT: MotorAxis.RIGHT_Z,
|
|
48
|
+
Mount.EXTENSION: MotorAxis.EXTENSION_Z,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_HARDWARE_AXIS_TO_MOTOR_AXIS: Dict[HardwareAxis, MotorAxis] = {
|
|
52
|
+
HardwareAxis.X: MotorAxis.X,
|
|
53
|
+
HardwareAxis.Y: MotorAxis.Y,
|
|
54
|
+
HardwareAxis.Z: MotorAxis.LEFT_Z,
|
|
55
|
+
HardwareAxis.A: MotorAxis.RIGHT_Z,
|
|
56
|
+
HardwareAxis.B: MotorAxis.LEFT_PLUNGER,
|
|
57
|
+
HardwareAxis.C: MotorAxis.RIGHT_PLUNGER,
|
|
58
|
+
HardwareAxis.P_L: MotorAxis.LEFT_PLUNGER,
|
|
59
|
+
HardwareAxis.P_R: MotorAxis.RIGHT_PLUNGER,
|
|
60
|
+
HardwareAxis.Z_L: MotorAxis.LEFT_Z,
|
|
61
|
+
HardwareAxis.Z_R: MotorAxis.RIGHT_Z,
|
|
62
|
+
HardwareAxis.Z_G: MotorAxis.EXTENSION_Z,
|
|
63
|
+
HardwareAxis.G: MotorAxis.EXTENSION_JAW,
|
|
64
|
+
HardwareAxis.Q: MotorAxis.AXIS_96_CHANNEL_CAM,
|
|
27
65
|
}
|
|
28
66
|
|
|
67
|
+
|
|
29
68
|
# The height of the bottom of the pipette nozzle at home position without any tips.
|
|
30
69
|
# We rely on this being the same for every OT-3 pipette.
|
|
31
70
|
#
|
|
@@ -36,6 +75,8 @@ _MOTOR_AXIS_TO_HARDWARE_AXIS: Dict[MotorAxis, HardwareAxis] = {
|
|
|
36
75
|
# That OT3Simulator return value is what Protocol Engine uses for simulation when Protocol Engine
|
|
37
76
|
# is configured to not virtualize pipettes, so this number should match it.
|
|
38
77
|
VIRTUAL_MAX_OT3_HEIGHT = 248.0
|
|
78
|
+
# This number was found by using the longest pipette's P1000V2 default configuration values.
|
|
79
|
+
VIRTUAL_MAX_OT2_HEIGHT = 268.14
|
|
39
80
|
|
|
40
81
|
|
|
41
82
|
class GantryMover(TypingProtocol):
|
|
@@ -50,16 +91,45 @@ class GantryMover(TypingProtocol):
|
|
|
50
91
|
"""Get the current position of the gantry."""
|
|
51
92
|
...
|
|
52
93
|
|
|
94
|
+
async def get_position_from_mount(
|
|
95
|
+
self,
|
|
96
|
+
mount: Mount,
|
|
97
|
+
critical_point: Optional[CriticalPoint] = None,
|
|
98
|
+
fail_on_not_homed: bool = False,
|
|
99
|
+
) -> Point:
|
|
100
|
+
"""Get the current position of the gantry based on the given mount."""
|
|
101
|
+
...
|
|
102
|
+
|
|
53
103
|
def get_max_travel_z(self, pipette_id: str) -> float:
|
|
54
104
|
"""Get the maximum allowed z-height for pipette movement."""
|
|
55
105
|
...
|
|
56
106
|
|
|
107
|
+
def get_max_travel_z_from_mount(self, mount: MountType) -> float:
|
|
108
|
+
"""Get the maximum allowed z-height for mount movement."""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
async def move_axes(
|
|
112
|
+
self,
|
|
113
|
+
axis_map: Dict[MotorAxis, float],
|
|
114
|
+
critical_point: Optional[Dict[MotorAxis, float]] = None,
|
|
115
|
+
speed: Optional[float] = None,
|
|
116
|
+
relative_move: bool = False,
|
|
117
|
+
) -> Dict[MotorAxis, float]:
|
|
118
|
+
"""Move a set of axes a given distance."""
|
|
119
|
+
...
|
|
120
|
+
|
|
57
121
|
async def move_to(
|
|
58
122
|
self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float]
|
|
59
123
|
) -> Point:
|
|
60
124
|
"""Move the hardware gantry to a waypoint."""
|
|
61
125
|
...
|
|
62
126
|
|
|
127
|
+
async def move_mount_to(
|
|
128
|
+
self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float]
|
|
129
|
+
) -> Point:
|
|
130
|
+
"""Move the provided hardware mount to a waypoint."""
|
|
131
|
+
...
|
|
132
|
+
|
|
63
133
|
async def move_relative(
|
|
64
134
|
self,
|
|
65
135
|
pipette_id: str,
|
|
@@ -85,6 +155,16 @@ class GantryMover(TypingProtocol):
|
|
|
85
155
|
"""Transform an engine motor axis into a hardware axis."""
|
|
86
156
|
...
|
|
87
157
|
|
|
158
|
+
def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount:
|
|
159
|
+
"""Find a mount axis in the axis_map if it exists otherwise default to left mount."""
|
|
160
|
+
...
|
|
161
|
+
|
|
162
|
+
def motor_axes_to_present_hardware_axes(
|
|
163
|
+
self, motor_axes: List[MotorAxis]
|
|
164
|
+
) -> List[HardwareAxis]:
|
|
165
|
+
"""Transform a list of engine axes into a list of hardware axes, filtering out non-present axes."""
|
|
166
|
+
...
|
|
167
|
+
|
|
88
168
|
|
|
89
169
|
class HardwareGantryMover(GantryMover):
|
|
90
170
|
"""Hardware API based gantry movement handler."""
|
|
@@ -93,10 +173,70 @@ class HardwareGantryMover(GantryMover):
|
|
|
93
173
|
self._hardware_api = hardware_api
|
|
94
174
|
self._state_view = state_view
|
|
95
175
|
|
|
176
|
+
def motor_axes_to_present_hardware_axes(
|
|
177
|
+
self, motor_axes: List[MotorAxis]
|
|
178
|
+
) -> List[HardwareAxis]:
|
|
179
|
+
"""Get hardware axes from engine axes while filtering out non-present axes."""
|
|
180
|
+
return [
|
|
181
|
+
self.motor_axis_to_hardware_axis(motor_axis)
|
|
182
|
+
for motor_axis in motor_axes
|
|
183
|
+
if self._hardware_api.axis_is_present(
|
|
184
|
+
self.motor_axis_to_hardware_axis(motor_axis)
|
|
185
|
+
)
|
|
186
|
+
]
|
|
187
|
+
|
|
96
188
|
def motor_axis_to_hardware_axis(self, motor_axis: MotorAxis) -> HardwareAxis:
|
|
97
189
|
"""Transform an engine motor axis into a hardware axis."""
|
|
98
190
|
return _MOTOR_AXIS_TO_HARDWARE_AXIS[motor_axis]
|
|
99
191
|
|
|
192
|
+
def _hardware_axis_to_motor_axis(self, motor_axis: HardwareAxis) -> MotorAxis:
|
|
193
|
+
"""Transform an hardware axis into a engine motor axis."""
|
|
194
|
+
return _HARDWARE_AXIS_TO_MOTOR_AXIS[motor_axis]
|
|
195
|
+
|
|
196
|
+
def _convert_axis_map_for_hw(
|
|
197
|
+
self, axis_map: Dict[MotorAxis, float]
|
|
198
|
+
) -> Dict[HardwareAxis, float]:
|
|
199
|
+
"""Transform an engine motor axis map to a hardware axis map."""
|
|
200
|
+
return {_MOTOR_AXIS_TO_HARDWARE_AXIS[ax]: dist for ax, dist in axis_map.items()}
|
|
201
|
+
|
|
202
|
+
def _critical_point_for(
|
|
203
|
+
self, mount: Mount, cp_override: Optional[Dict[MotorAxis, float]] = None
|
|
204
|
+
) -> Point:
|
|
205
|
+
if cp_override:
|
|
206
|
+
return Point(
|
|
207
|
+
x=cp_override[MotorAxis.X],
|
|
208
|
+
y=cp_override[MotorAxis.Y],
|
|
209
|
+
z=cp_override[_HARDWARE_MOUNT_MOTOR_AXIS_TO[mount]],
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
return self._hardware_api.critical_point_for(mount)
|
|
213
|
+
|
|
214
|
+
def _get_gantry_offsets_for_robot_type(
|
|
215
|
+
self,
|
|
216
|
+
) -> Tuple[Point, Point, Optional[Point]]:
|
|
217
|
+
if isinstance(self._hardware_api.config, OT3Config):
|
|
218
|
+
return (
|
|
219
|
+
Point(*self._hardware_api.config.left_mount_offset),
|
|
220
|
+
Point(*self._hardware_api.config.right_mount_offset),
|
|
221
|
+
Point(*self._hardware_api.config.gripper_mount_offset),
|
|
222
|
+
)
|
|
223
|
+
else:
|
|
224
|
+
return (
|
|
225
|
+
Point(*self._hardware_api.config.left_mount_offset),
|
|
226
|
+
Point(0, 0, 0),
|
|
227
|
+
None,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount:
|
|
231
|
+
"""Find a mount axis in the axis_map if it exists otherwise default to left mount."""
|
|
232
|
+
found_mount = Mount.LEFT
|
|
233
|
+
mounts = list(_MOTOR_AXIS_TO_HARDWARE_MOUNT.keys())
|
|
234
|
+
for k in axis_map.keys():
|
|
235
|
+
if k in mounts:
|
|
236
|
+
found_mount = _MOTOR_AXIS_TO_HARDWARE_MOUNT[k]
|
|
237
|
+
break
|
|
238
|
+
return found_mount
|
|
239
|
+
|
|
100
240
|
async def get_position(
|
|
101
241
|
self,
|
|
102
242
|
pipette_id: str,
|
|
@@ -114,12 +254,33 @@ class HardwareGantryMover(GantryMover):
|
|
|
114
254
|
pipette_id=pipette_id,
|
|
115
255
|
current_location=current_well,
|
|
116
256
|
)
|
|
257
|
+
point = await self.get_position_from_mount(
|
|
258
|
+
mount=pipette_location.mount.to_hw_mount(),
|
|
259
|
+
critical_point=pipette_location.critical_point,
|
|
260
|
+
fail_on_not_homed=fail_on_not_homed,
|
|
261
|
+
)
|
|
262
|
+
return point
|
|
263
|
+
|
|
264
|
+
async def get_position_from_mount(
|
|
265
|
+
self,
|
|
266
|
+
mount: Mount,
|
|
267
|
+
critical_point: Optional[CriticalPoint] = None,
|
|
268
|
+
fail_on_not_homed: bool = False,
|
|
269
|
+
) -> Point:
|
|
270
|
+
"""Get the current position of the gantry based on the mount.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
mount: The mount to get the position for.
|
|
274
|
+
critical_point: Optional parameter for getting instrument location data, effects critical point.
|
|
275
|
+
fail_on_not_homed: Raise PositionUnknownError if gantry position is not known.
|
|
276
|
+
"""
|
|
117
277
|
try:
|
|
118
|
-
|
|
119
|
-
mount=
|
|
120
|
-
critical_point=
|
|
278
|
+
point = await self._hardware_api.gantry_position(
|
|
279
|
+
mount=mount,
|
|
280
|
+
critical_point=critical_point,
|
|
121
281
|
fail_on_not_homed=fail_on_not_homed,
|
|
122
282
|
)
|
|
283
|
+
return point
|
|
123
284
|
except PositionUnknownError as e:
|
|
124
285
|
raise MustHomeError(message=str(e), wrapping=[e])
|
|
125
286
|
|
|
@@ -129,8 +290,16 @@ class HardwareGantryMover(GantryMover):
|
|
|
129
290
|
Args:
|
|
130
291
|
pipette_id: Pipette ID to get max travel z-height for.
|
|
131
292
|
"""
|
|
132
|
-
|
|
133
|
-
return self.
|
|
293
|
+
mount = self._state_view.pipettes.get_mount(pipette_id)
|
|
294
|
+
return self.get_max_travel_z_from_mount(mount=mount)
|
|
295
|
+
|
|
296
|
+
def get_max_travel_z_from_mount(self, mount: MountType) -> float:
|
|
297
|
+
"""Get the maximum allowed z-height for any mount movement.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
mount: Mount to get max travel z-height for.
|
|
301
|
+
"""
|
|
302
|
+
return self._hardware_api.get_instrument_max_height(mount=mount.to_hw_mount())
|
|
134
303
|
|
|
135
304
|
async def move_to(
|
|
136
305
|
self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float]
|
|
@@ -150,6 +319,88 @@ class HardwareGantryMover(GantryMover):
|
|
|
150
319
|
|
|
151
320
|
return waypoints[-1].position
|
|
152
321
|
|
|
322
|
+
async def move_mount_to(
|
|
323
|
+
self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float]
|
|
324
|
+
) -> Point:
|
|
325
|
+
"""Move the given hardware mount to a waypoint."""
|
|
326
|
+
assert len(waypoints) > 0, "Must have at least one waypoint"
|
|
327
|
+
for waypoint in waypoints:
|
|
328
|
+
log.info(f"The current waypoint moving is {waypoint}")
|
|
329
|
+
await self._hardware_api.move_to(
|
|
330
|
+
mount=mount,
|
|
331
|
+
abs_position=waypoint.position,
|
|
332
|
+
critical_point=waypoint.critical_point,
|
|
333
|
+
speed=speed,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return waypoints[-1].position
|
|
337
|
+
|
|
338
|
+
async def move_axes(
|
|
339
|
+
self,
|
|
340
|
+
axis_map: Dict[MotorAxis, float],
|
|
341
|
+
critical_point: Optional[Dict[MotorAxis, float]] = None,
|
|
342
|
+
speed: Optional[float] = None,
|
|
343
|
+
relative_move: bool = False,
|
|
344
|
+
) -> Dict[MotorAxis, float]:
|
|
345
|
+
"""Move a set of axes a given distance.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
axis_map: The mapping of axes to command.
|
|
349
|
+
critical_point: A critical point override for axes
|
|
350
|
+
speed: Optional speed parameter for the move.
|
|
351
|
+
relative_move: Whether the axis map needs to be converted from a relative to absolute move.
|
|
352
|
+
"""
|
|
353
|
+
try:
|
|
354
|
+
pos_hw = self._convert_axis_map_for_hw(axis_map)
|
|
355
|
+
mount = self.pick_mount_from_axis_map(axis_map)
|
|
356
|
+
if relative_move:
|
|
357
|
+
current_position = await self._hardware_api.current_position(
|
|
358
|
+
mount, refresh=True
|
|
359
|
+
)
|
|
360
|
+
log.info(f"The current position of the robot is: {current_position}.")
|
|
361
|
+
converted_current_position_deck = (
|
|
362
|
+
self._hardware_api.get_deck_from_machine(current_position)
|
|
363
|
+
)
|
|
364
|
+
log.info(f"The current position of the robot is: {current_position}.")
|
|
365
|
+
|
|
366
|
+
pos_hw = target_axis_map_from_relative(pos_hw, current_position)
|
|
367
|
+
log.info(
|
|
368
|
+
f"The absolute position is: {pos_hw} and hw pos map is {pos_hw}."
|
|
369
|
+
)
|
|
370
|
+
log.info(f"The calculated move {pos_hw} and {mount}")
|
|
371
|
+
(
|
|
372
|
+
left_offset,
|
|
373
|
+
right_offset,
|
|
374
|
+
gripper_offset,
|
|
375
|
+
) = self._get_gantry_offsets_for_robot_type()
|
|
376
|
+
absolute_pos = target_axis_map_from_absolute(
|
|
377
|
+
mount,
|
|
378
|
+
pos_hw,
|
|
379
|
+
partial(self._critical_point_for, cp_override=critical_point),
|
|
380
|
+
left_mount_offset=left_offset,
|
|
381
|
+
right_mount_offset=right_offset,
|
|
382
|
+
gripper_mount_offset=gripper_offset,
|
|
383
|
+
)
|
|
384
|
+
log.info(f"The prepped abs {absolute_pos}")
|
|
385
|
+
await self._hardware_api.move_axes(
|
|
386
|
+
position=absolute_pos,
|
|
387
|
+
speed=speed,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
except PositionUnknownError as e:
|
|
391
|
+
raise MustHomeError(message=str(e), wrapping=[e])
|
|
392
|
+
|
|
393
|
+
current_position = await self._hardware_api.current_position(
|
|
394
|
+
mount, refresh=True
|
|
395
|
+
)
|
|
396
|
+
converted_current_position_deck = self._hardware_api.get_deck_from_machine(
|
|
397
|
+
current_position
|
|
398
|
+
)
|
|
399
|
+
return {
|
|
400
|
+
self._hardware_axis_to_motor_axis(ax): pos
|
|
401
|
+
for ax, pos in converted_current_position_deck.items()
|
|
402
|
+
}
|
|
403
|
+
|
|
153
404
|
async def move_relative(
|
|
154
405
|
self,
|
|
155
406
|
pipette_id: str,
|
|
@@ -239,6 +490,16 @@ class VirtualGantryMover(GantryMover):
|
|
|
239
490
|
"""Transform an engine motor axis into a hardware axis."""
|
|
240
491
|
return _MOTOR_AXIS_TO_HARDWARE_AXIS[motor_axis]
|
|
241
492
|
|
|
493
|
+
def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount:
|
|
494
|
+
"""Find a mount axis in the axis_map if it exists otherwise default to left mount."""
|
|
495
|
+
found_mount = Mount.LEFT
|
|
496
|
+
mounts = list(_MOTOR_AXIS_TO_HARDWARE_MOUNT.keys())
|
|
497
|
+
for k in axis_map.keys():
|
|
498
|
+
if k in mounts:
|
|
499
|
+
found_mount = _MOTOR_AXIS_TO_HARDWARE_MOUNT[k]
|
|
500
|
+
break
|
|
501
|
+
return found_mount
|
|
502
|
+
|
|
242
503
|
async def get_position(
|
|
243
504
|
self,
|
|
244
505
|
pipette_id: str,
|
|
@@ -261,6 +522,31 @@ class VirtualGantryMover(GantryMover):
|
|
|
261
522
|
origin = Point(x=0, y=0, z=0)
|
|
262
523
|
return origin
|
|
263
524
|
|
|
525
|
+
async def get_position_from_mount(
|
|
526
|
+
self,
|
|
527
|
+
mount: Mount,
|
|
528
|
+
critical_point: Optional[CriticalPoint] = None,
|
|
529
|
+
fail_on_not_homed: bool = False,
|
|
530
|
+
) -> Point:
|
|
531
|
+
"""Get the current position of the gantry based on the mount.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
mount: The mount to get the position for.
|
|
535
|
+
critical_point: Optional parameter for getting instrument location data, effects critical point.
|
|
536
|
+
fail_on_not_homed: Raise PositionUnknownError if gantry position is not known.
|
|
537
|
+
"""
|
|
538
|
+
pipette = self._state_view.pipettes.get_by_mount(MountType[mount.name])
|
|
539
|
+
origin_deck_point = (
|
|
540
|
+
self._state_view.pipettes.get_deck_point(pipette.id) if pipette else None
|
|
541
|
+
)
|
|
542
|
+
if origin_deck_point is not None:
|
|
543
|
+
origin = Point(
|
|
544
|
+
x=origin_deck_point.x, y=origin_deck_point.y, z=origin_deck_point.z
|
|
545
|
+
)
|
|
546
|
+
else:
|
|
547
|
+
origin = Point(x=0, y=0, z=0)
|
|
548
|
+
return origin
|
|
549
|
+
|
|
264
550
|
def get_max_travel_z(self, pipette_id: str) -> float:
|
|
265
551
|
"""Get the maximum allowed z-height for pipette movement.
|
|
266
552
|
|
|
@@ -278,6 +564,68 @@ class VirtualGantryMover(GantryMover):
|
|
|
278
564
|
tip_length = tip.length if tip is not None else 0
|
|
279
565
|
return instrument_height - tip_length
|
|
280
566
|
|
|
567
|
+
def get_max_travel_z_from_mount(self, mount: MountType) -> float:
|
|
568
|
+
"""Get the maximum allowed z-height for mount."""
|
|
569
|
+
pipette = self._state_view.pipettes.get_by_mount(mount)
|
|
570
|
+
if self._state_view.config.robot_type == "OT-2 Standard":
|
|
571
|
+
instrument_height = (
|
|
572
|
+
self._state_view.pipettes.get_instrument_max_height_ot2(pipette.id)
|
|
573
|
+
if pipette
|
|
574
|
+
else VIRTUAL_MAX_OT2_HEIGHT
|
|
575
|
+
)
|
|
576
|
+
else:
|
|
577
|
+
instrument_height = VIRTUAL_MAX_OT3_HEIGHT
|
|
578
|
+
if pipette:
|
|
579
|
+
tip = self._state_view.pipettes.get_attached_tip(pipette_id=pipette.id)
|
|
580
|
+
tip_length = tip.length if tip is not None else 0.0
|
|
581
|
+
else:
|
|
582
|
+
tip_length = 0.0
|
|
583
|
+
return instrument_height - tip_length
|
|
584
|
+
|
|
585
|
+
async def move_axes(
|
|
586
|
+
self,
|
|
587
|
+
axis_map: Dict[MotorAxis, float],
|
|
588
|
+
critical_point: Optional[Dict[MotorAxis, float]] = None,
|
|
589
|
+
speed: Optional[float] = None,
|
|
590
|
+
relative_move: bool = False,
|
|
591
|
+
) -> Dict[MotorAxis, float]:
|
|
592
|
+
"""Move the give axes map. No-op in virtual implementation."""
|
|
593
|
+
mount = self.pick_mount_from_axis_map(axis_map)
|
|
594
|
+
current_position = await self.get_position_from_mount(mount)
|
|
595
|
+
updated_position = {}
|
|
596
|
+
if relative_move:
|
|
597
|
+
updated_position[MotorAxis.X] = (
|
|
598
|
+
axis_map.get(MotorAxis.X, 0.0) + current_position[0]
|
|
599
|
+
)
|
|
600
|
+
updated_position[MotorAxis.Y] = (
|
|
601
|
+
axis_map.get(MotorAxis.Y, 0.0) + current_position[1]
|
|
602
|
+
)
|
|
603
|
+
if mount == Mount.RIGHT:
|
|
604
|
+
updated_position[MotorAxis.RIGHT_Z] = (
|
|
605
|
+
axis_map.get(MotorAxis.RIGHT_Z, 0.0) + current_position[2]
|
|
606
|
+
)
|
|
607
|
+
elif mount == Mount.EXTENSION:
|
|
608
|
+
updated_position[MotorAxis.EXTENSION_Z] = (
|
|
609
|
+
axis_map.get(MotorAxis.EXTENSION_Z, 0.0) + current_position[2]
|
|
610
|
+
)
|
|
611
|
+
else:
|
|
612
|
+
updated_position[MotorAxis.LEFT_Z] = (
|
|
613
|
+
axis_map.get(MotorAxis.LEFT_Z, 0.0) + current_position[2]
|
|
614
|
+
)
|
|
615
|
+
else:
|
|
616
|
+
critical_point = critical_point or {}
|
|
617
|
+
updated_position = {
|
|
618
|
+
ax: pos - critical_point.get(ax, 0.0) for ax, pos in axis_map.items()
|
|
619
|
+
}
|
|
620
|
+
return updated_position
|
|
621
|
+
|
|
622
|
+
async def move_mount_to(
|
|
623
|
+
self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float]
|
|
624
|
+
) -> Point:
|
|
625
|
+
"""Move the hardware mount to a waypoint. No-op in virtual implementation."""
|
|
626
|
+
assert len(waypoints) > 0, "Must have at least one waypoint"
|
|
627
|
+
return waypoints[-1].position
|
|
628
|
+
|
|
281
629
|
async def move_to(
|
|
282
630
|
self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float]
|
|
283
631
|
) -> Point:
|
|
@@ -313,6 +661,14 @@ class VirtualGantryMover(GantryMover):
|
|
|
313
661
|
"""Retract the 'idle' mount if necessary."""
|
|
314
662
|
pass
|
|
315
663
|
|
|
664
|
+
def motor_axes_to_present_hardware_axes(
|
|
665
|
+
self, motor_axes: List[MotorAxis]
|
|
666
|
+
) -> List[HardwareAxis]:
|
|
667
|
+
"""Get present hardware axes from a list of engine axes. In simulation, all axes are present."""
|
|
668
|
+
return [
|
|
669
|
+
self.motor_axis_to_hardware_axis(motor_axis) for motor_axis in motor_axes
|
|
670
|
+
]
|
|
671
|
+
|
|
316
672
|
|
|
317
673
|
def create_gantry_mover(
|
|
318
674
|
state_view: StateView, hardware_api: HardwareControlAPI
|
|
@@ -149,6 +149,33 @@ class MovementHandler:
|
|
|
149
149
|
|
|
150
150
|
return final_point
|
|
151
151
|
|
|
152
|
+
async def move_mount_to(
|
|
153
|
+
self, mount: MountType, destination: DeckPoint, speed: Optional[float] = None
|
|
154
|
+
) -> Point:
|
|
155
|
+
"""Move mount to a specific location on the deck."""
|
|
156
|
+
hw_mount = mount.to_hw_mount()
|
|
157
|
+
await self._gantry_mover.prepare_for_mount_movement(hw_mount)
|
|
158
|
+
origin = await self._gantry_mover.get_position_from_mount(mount=hw_mount)
|
|
159
|
+
max_travel_z = self._gantry_mover.get_max_travel_z_from_mount(mount=mount)
|
|
160
|
+
|
|
161
|
+
# calculate the movement's waypoints
|
|
162
|
+
waypoints = self._state_store.motion.get_movement_waypoints_to_coords(
|
|
163
|
+
origin=origin,
|
|
164
|
+
dest=Point(x=destination.x, y=destination.y, z=destination.z),
|
|
165
|
+
max_travel_z=max_travel_z,
|
|
166
|
+
direct=False,
|
|
167
|
+
additional_min_travel_z=None,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# move through the waypoints
|
|
171
|
+
final_point = await self._gantry_mover.move_mount_to(
|
|
172
|
+
mount=hw_mount,
|
|
173
|
+
waypoints=waypoints,
|
|
174
|
+
speed=speed,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return final_point
|
|
178
|
+
|
|
152
179
|
async def move_to_addressable_area(
|
|
153
180
|
self,
|
|
154
181
|
pipette_id: str,
|
|
@@ -91,7 +91,11 @@ class HardwarePipettingHandler(PipettingHandler):
|
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
async def prepare_for_aspirate(self, pipette_id: str) -> None:
|
|
94
|
-
"""Prepare for pipette aspiration.
|
|
94
|
+
"""Prepare for pipette aspiration.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
PipetteOverpressureError, propagated as-is from the hardware controller.
|
|
98
|
+
"""
|
|
95
99
|
hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()
|
|
96
100
|
await self._hardware_api.prepare_for_aspirate(mount=hw_mount)
|
|
97
101
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Tip pickup and drop procedures."""
|
|
2
|
+
|
|
2
3
|
from typing import Optional, Dict
|
|
3
4
|
from typing_extensions import Protocol as TypingProtocol
|
|
4
5
|
|
|
5
6
|
from opentrons.hardware_control import HardwareControlAPI
|
|
6
7
|
from opentrons.hardware_control.types import FailedTipStateCheck, InstrumentProbeType
|
|
7
8
|
from opentrons.protocol_engine.errors.exceptions import PickUpTipTipNotAttachedError
|
|
8
|
-
from opentrons.types import Mount
|
|
9
|
+
from opentrons.types import Mount, NozzleConfigurationType
|
|
9
10
|
|
|
10
11
|
from opentrons_shared_data.errors.exceptions import (
|
|
11
12
|
CommandPreconditionViolated,
|
|
@@ -23,9 +24,6 @@ from ..errors import (
|
|
|
23
24
|
ProtocolEngineError,
|
|
24
25
|
)
|
|
25
26
|
|
|
26
|
-
from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
|
|
27
|
-
|
|
28
|
-
|
|
29
27
|
PRIMARY_NOZZLE_TO_ENDING_NOZZLE_MAP = {
|
|
30
28
|
"A1": {"COLUMN": "H1", "ROW": "A12"},
|
|
31
29
|
"H1": {"COLUMN": "A1", "ROW": "H12"},
|
|
@@ -326,8 +324,8 @@ class HardwareTipHandler(TipHandler):
|
|
|
326
324
|
follow_singular_sensor: Optional[InstrumentProbeType] = None,
|
|
327
325
|
) -> None:
|
|
328
326
|
"""See documentation on abstract base class."""
|
|
329
|
-
nozzle_configuration = (
|
|
330
|
-
|
|
327
|
+
nozzle_configuration = self._state_view.pipettes.get_nozzle_configuration(
|
|
328
|
+
pipette_id=pipette_id
|
|
331
329
|
)
|
|
332
330
|
|
|
333
331
|
# Configuration metrics by which tip presence checking is ignored
|
|
@@ -35,7 +35,7 @@ def make_error_recovery_debug_note(type: "ErrorRecoveryType") -> CommandNote:
|
|
|
35
35
|
This is intended to be read by developers and support people, not computers.
|
|
36
36
|
"""
|
|
37
37
|
message = f"Handling this command failure with {type.name}."
|
|
38
|
-
return CommandNote.
|
|
38
|
+
return CommandNote.model_construct(
|
|
39
39
|
noteKind="debugErrorRecovery",
|
|
40
40
|
shortMessage=message,
|
|
41
41
|
longMessage=message,
|