opentrons 8.1.0a0__py2.py3-none-any.whl → 8.2.0a0__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.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/cli/analyze.py +71 -7
- opentrons/config/__init__.py +9 -0
- opentrons/config/advanced_settings.py +22 -0
- opentrons/config/defaults_ot3.py +14 -36
- opentrons/config/feature_flags.py +4 -0
- opentrons/config/types.py +6 -17
- opentrons/drivers/absorbance_reader/abstract.py +27 -3
- opentrons/drivers/absorbance_reader/async_byonoy.py +207 -154
- opentrons/drivers/absorbance_reader/driver.py +24 -15
- opentrons/drivers/absorbance_reader/hid_protocol.py +79 -50
- opentrons/drivers/absorbance_reader/simulator.py +32 -6
- opentrons/drivers/types.py +23 -1
- opentrons/execute.py +2 -2
- opentrons/hardware_control/api.py +18 -10
- opentrons/hardware_control/backends/controller.py +3 -2
- opentrons/hardware_control/backends/flex_protocol.py +11 -5
- opentrons/hardware_control/backends/ot3controller.py +18 -50
- opentrons/hardware_control/backends/ot3simulator.py +7 -6
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +22 -82
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -2
- opentrons/hardware_control/module_control.py +43 -2
- opentrons/hardware_control/modules/__init__.py +7 -1
- opentrons/hardware_control/modules/absorbance_reader.py +230 -83
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/heater_shaker.py +8 -3
- opentrons/hardware_control/modules/magdeck.py +12 -3
- opentrons/hardware_control/modules/mod_abc.py +27 -2
- opentrons/hardware_control/modules/tempdeck.py +15 -7
- opentrons/hardware_control/modules/thermocycler.py +69 -3
- opentrons/hardware_control/modules/types.py +11 -5
- opentrons/hardware_control/modules/update.py +11 -5
- opentrons/hardware_control/modules/utils.py +3 -1
- opentrons/hardware_control/ot3_calibration.py +6 -6
- opentrons/hardware_control/ot3api.py +126 -89
- opentrons/hardware_control/poller.py +15 -11
- opentrons/hardware_control/protocols/__init__.py +1 -7
- opentrons/hardware_control/protocols/instrument_configurer.py +14 -2
- opentrons/hardware_control/protocols/liquid_handler.py +5 -0
- opentrons/motion_planning/__init__.py +2 -0
- opentrons/motion_planning/waypoints.py +32 -0
- opentrons/protocol_api/__init__.py +2 -1
- opentrons/protocol_api/_liquid.py +87 -1
- opentrons/protocol_api/_parameter_context.py +10 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +0 -297
- opentrons/protocol_api/core/engine/instrument.py +29 -25
- opentrons/protocol_api/core/engine/labware.py +10 -2
- opentrons/protocol_api/core/engine/module_core.py +129 -17
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +355 -0
- opentrons/protocol_api/core/engine/protocol.py +55 -2
- opentrons/protocol_api/core/instrument.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +5 -2
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +2 -0
- opentrons/protocol_api/core/module.py +22 -4
- opentrons/protocol_api/core/protocol.py +5 -2
- opentrons/protocol_api/instrument_context.py +52 -20
- opentrons/protocol_api/labware.py +13 -1
- opentrons/protocol_api/module_contexts.py +68 -13
- opentrons/protocol_api/protocol_context.py +38 -4
- opentrons/protocol_api/validation.py +5 -3
- opentrons/protocol_engine/__init__.py +10 -9
- opentrons/protocol_engine/actions/__init__.py +5 -0
- opentrons/protocol_engine/actions/actions.py +42 -25
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/sync_client.py +7 -1
- opentrons/protocol_engine/clients/transports.py +1 -1
- opentrons/protocol_engine/commands/__init__.py +0 -4
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +41 -11
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +161 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +53 -9
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +160 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +196 -0
- opentrons/protocol_engine/commands/aspirate.py +29 -16
- opentrons/protocol_engine/commands/aspirate_in_place.py +32 -15
- opentrons/protocol_engine/commands/blow_out.py +63 -14
- opentrons/protocol_engine/commands/blow_out_in_place.py +55 -13
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +2 -5
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +3 -4
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +2 -5
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +6 -4
- opentrons/protocol_engine/commands/command.py +28 -17
- opentrons/protocol_engine/commands/command_unions.py +37 -24
- opentrons/protocol_engine/commands/comment.py +5 -3
- opentrons/protocol_engine/commands/configure_for_volume.py +11 -14
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +9 -15
- opentrons/protocol_engine/commands/custom.py +5 -3
- opentrons/protocol_engine/commands/dispense.py +42 -20
- opentrons/protocol_engine/commands/dispense_in_place.py +32 -14
- opentrons/protocol_engine/commands/drop_tip.py +68 -15
- opentrons/protocol_engine/commands/drop_tip_in_place.py +52 -11
- opentrons/protocol_engine/commands/get_tip_presence.py +5 -3
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +6 -6
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +8 -6
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +8 -4
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +6 -4
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/home.py +11 -5
- opentrons/protocol_engine/commands/liquid_probe.py +146 -88
- opentrons/protocol_engine/commands/load_labware.py +19 -5
- opentrons/protocol_engine/commands/load_liquid.py +18 -7
- opentrons/protocol_engine/commands/load_module.py +43 -6
- opentrons/protocol_engine/commands/load_pipette.py +18 -17
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +6 -6
- opentrons/protocol_engine/commands/magnetic_module/engage.py +6 -4
- opentrons/protocol_engine/commands/move_labware.py +106 -19
- opentrons/protocol_engine/commands/move_relative.py +15 -3
- opentrons/protocol_engine/commands/move_to_addressable_area.py +29 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +13 -4
- opentrons/protocol_engine/commands/move_to_coordinates.py +11 -5
- opentrons/protocol_engine/commands/move_to_well.py +37 -10
- opentrons/protocol_engine/commands/pick_up_tip.py +50 -29
- opentrons/protocol_engine/commands/pipetting_common.py +39 -15
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +62 -15
- opentrons/protocol_engine/commands/reload_labware.py +13 -4
- opentrons/protocol_engine/commands/retract_axis.py +6 -3
- opentrons/protocol_engine/commands/save_position.py +2 -3
- opentrons/protocol_engine/commands/set_rail_lights.py +5 -3
- opentrons/protocol_engine/commands/set_status_bar.py +5 -3
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +6 -4
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +3 -4
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/__init__.py +19 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +8 -8
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +8 -4
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +165 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +6 -6
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +3 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +6 -4
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +6 -4
- opentrons/protocol_engine/commands/touch_tip.py +19 -7
- opentrons/protocol_engine/commands/unsafe/__init__.py +30 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +6 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +5 -3
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +194 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +75 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +5 -3
- opentrons/protocol_engine/commands/verify_tip_presence.py +5 -5
- opentrons/protocol_engine/commands/wait_for_duration.py +5 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +5 -3
- opentrons/protocol_engine/create_protocol_engine.py +41 -8
- opentrons/protocol_engine/engine_support.py +2 -1
- opentrons/protocol_engine/error_recovery_policy.py +14 -3
- opentrons/protocol_engine/errors/__init__.py +18 -0
- opentrons/protocol_engine/errors/exceptions.py +114 -2
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +22 -13
- opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
- opentrons/protocol_engine/execution/door_watcher.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +2 -1
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +4 -2
- opentrons/protocol_engine/execution/hardware_stopper.py +3 -3
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +1 -4
- opentrons/protocol_engine/execution/labware_movement.py +6 -3
- opentrons/protocol_engine/execution/movement.py +8 -3
- opentrons/protocol_engine/execution/pipetting.py +7 -4
- opentrons/protocol_engine/execution/queue_worker.py +6 -2
- opentrons/protocol_engine/execution/run_control.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +1 -1
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +2 -1
- opentrons/protocol_engine/execution/tip_handler.py +77 -43
- opentrons/protocol_engine/notes/__init__.py +14 -2
- opentrons/protocol_engine/notes/notes.py +18 -1
- opentrons/protocol_engine/plugins.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +54 -31
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +58 -5
- opentrons/protocol_engine/resources/file_provider.py +157 -0
- opentrons/protocol_engine/resources/fixture_validation.py +5 -0
- opentrons/protocol_engine/resources/labware_validation.py +10 -0
- opentrons/protocol_engine/state/__init__.py +0 -70
- opentrons/protocol_engine/state/addressable_areas.py +1 -1
- opentrons/protocol_engine/state/command_history.py +21 -2
- opentrons/protocol_engine/state/commands.py +110 -31
- opentrons/protocol_engine/state/files.py +59 -0
- opentrons/protocol_engine/state/frustum_helpers.py +440 -0
- opentrons/protocol_engine/state/geometry.py +359 -15
- opentrons/protocol_engine/state/labware.py +166 -63
- opentrons/protocol_engine/state/liquids.py +1 -1
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +19 -3
- opentrons/protocol_engine/state/modules.py +167 -85
- opentrons/protocol_engine/state/motion.py +16 -9
- opentrons/protocol_engine/state/pipettes.py +157 -317
- opentrons/protocol_engine/state/state.py +30 -1
- opentrons/protocol_engine/state/state_summary.py +3 -0
- opentrons/protocol_engine/state/tips.py +69 -114
- opentrons/protocol_engine/state/update_types.py +408 -0
- opentrons/protocol_engine/state/wells.py +236 -0
- opentrons/protocol_engine/types.py +90 -0
- opentrons/protocol_reader/file_format_validator.py +83 -15
- opentrons/protocol_runner/json_translator.py +21 -5
- opentrons/protocol_runner/legacy_command_mapper.py +27 -6
- opentrons/protocol_runner/legacy_context_plugin.py +27 -71
- opentrons/protocol_runner/protocol_runner.py +6 -3
- opentrons/protocol_runner/run_orchestrator.py +26 -6
- opentrons/protocols/advanced_control/mix.py +3 -5
- opentrons/protocols/advanced_control/transfers.py +125 -56
- opentrons/protocols/api_support/constants.py +1 -1
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/labware_like.py +4 -4
- opentrons/protocols/api_support/tip_tracker.py +2 -2
- opentrons/protocols/api_support/types.py +15 -2
- opentrons/protocols/api_support/util.py +30 -42
- opentrons/protocols/duration/errors.py +1 -1
- opentrons/protocols/duration/estimator.py +50 -29
- opentrons/protocols/execution/dev_types.py +2 -2
- opentrons/protocols/execution/execute_json_v4.py +15 -10
- opentrons/protocols/execution/execute_python.py +8 -3
- opentrons/protocols/geometry/planning.py +12 -12
- opentrons/protocols/labware.py +17 -33
- opentrons/simulate.py +3 -3
- opentrons/types.py +30 -3
- opentrons/util/logging_config.py +34 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/METADATA +5 -4
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/RECORD +227 -215
- opentrons/protocol_engine/commands/absorbance_reader/measure.py +0 -94
- opentrons/protocol_engine/commands/configuring_common.py +0 -26
- opentrons/protocol_runner/thread_async_queue.py +0 -174
- /opentrons/protocol_engine/state/{abstract_store.py → _abstract_store.py} +0 -0
- /opentrons/protocol_engine/state/{move_types.py → _move_types.py} +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/LICENSE +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/WHEEL +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.1.0a0.dist-info → opentrons-8.2.0a0.dist-info}/top_level.txt +0 -0
|
@@ -12,20 +12,24 @@ from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN
|
|
|
12
12
|
from opentrons_shared_data.deck.types import CutoutFixture
|
|
13
13
|
from opentrons_shared_data.pipette import PIPETTE_X_SPAN
|
|
14
14
|
from opentrons_shared_data.pipette.types import ChannelCount
|
|
15
|
+
from opentrons.protocols.models import LabwareDefinition
|
|
15
16
|
|
|
16
17
|
from .. import errors
|
|
17
18
|
from ..errors import (
|
|
18
19
|
LabwareNotLoadedOnLabwareError,
|
|
19
20
|
LabwareNotLoadedOnModuleError,
|
|
20
21
|
LabwareMovementNotAllowedError,
|
|
22
|
+
OperationLocationNotInWellError,
|
|
21
23
|
)
|
|
22
|
-
from ..resources import fixture_validation
|
|
24
|
+
from ..resources import fixture_validation, labware_validation
|
|
23
25
|
from ..types import (
|
|
24
26
|
OFF_DECK_LOCATION,
|
|
25
27
|
LoadedLabware,
|
|
26
28
|
LoadedModule,
|
|
27
29
|
WellLocation,
|
|
30
|
+
LiquidHandlingWellLocation,
|
|
28
31
|
DropTipWellLocation,
|
|
32
|
+
PickUpTipWellLocation,
|
|
29
33
|
WellOrigin,
|
|
30
34
|
DropTipWellOrigin,
|
|
31
35
|
WellOffset,
|
|
@@ -45,12 +49,18 @@ from ..types import (
|
|
|
45
49
|
AddressableOffsetVector,
|
|
46
50
|
StagingSlotLocation,
|
|
47
51
|
LabwareOffsetLocation,
|
|
52
|
+
ModuleModel,
|
|
48
53
|
)
|
|
49
54
|
from .config import Config
|
|
50
55
|
from .labware import LabwareView
|
|
56
|
+
from .wells import WellView
|
|
51
57
|
from .modules import ModuleView
|
|
52
58
|
from .pipettes import PipetteView
|
|
53
59
|
from .addressable_areas import AddressableAreaView
|
|
60
|
+
from .frustum_helpers import (
|
|
61
|
+
find_volume_at_well_height,
|
|
62
|
+
find_height_at_well_volume,
|
|
63
|
+
)
|
|
54
64
|
|
|
55
65
|
|
|
56
66
|
SLOT_WIDTH = 128
|
|
@@ -96,6 +106,7 @@ class GeometryView:
|
|
|
96
106
|
self,
|
|
97
107
|
config: Config,
|
|
98
108
|
labware_view: LabwareView,
|
|
109
|
+
well_view: WellView,
|
|
99
110
|
module_view: ModuleView,
|
|
100
111
|
pipette_view: PipetteView,
|
|
101
112
|
addressable_area_view: AddressableAreaView,
|
|
@@ -103,6 +114,7 @@ class GeometryView:
|
|
|
103
114
|
"""Initialize a GeometryView instance."""
|
|
104
115
|
self._config = config
|
|
105
116
|
self._labware = labware_view
|
|
117
|
+
self._wells = well_view
|
|
106
118
|
self._modules = module_view
|
|
107
119
|
self._pipettes = pipette_view
|
|
108
120
|
self._addressable_areas = addressable_area_view
|
|
@@ -252,11 +264,16 @@ class GeometryView:
|
|
|
252
264
|
def get_labware_parent_nominal_position(self, labware_id: str) -> Point:
|
|
253
265
|
"""Get the position of the labware's uncalibrated parent slot (deck, module, or another labware)."""
|
|
254
266
|
try:
|
|
255
|
-
|
|
267
|
+
addressable_area_name = self.get_ancestor_slot_name(labware_id).id
|
|
256
268
|
except errors.LocationIsStagingSlotError:
|
|
257
|
-
|
|
258
|
-
|
|
269
|
+
addressable_area_name = self._get_staging_slot_name(labware_id)
|
|
270
|
+
except errors.LocationIsLidDockSlotError:
|
|
271
|
+
addressable_area_name = self._get_lid_dock_slot_name(labware_id)
|
|
272
|
+
slot_pos = self._addressable_areas.get_addressable_area_position(
|
|
273
|
+
addressable_area_name
|
|
274
|
+
)
|
|
259
275
|
labware_data = self._labware.get(labware_id)
|
|
276
|
+
|
|
260
277
|
offset = self._get_labware_position_offset(labware_id, labware_data.location)
|
|
261
278
|
|
|
262
279
|
return Point(
|
|
@@ -405,11 +422,51 @@ class GeometryView:
|
|
|
405
422
|
z=origin_pos.z + cal_offset.z,
|
|
406
423
|
)
|
|
407
424
|
|
|
425
|
+
WellLocations = Union[
|
|
426
|
+
WellLocation, LiquidHandlingWellLocation, PickUpTipWellLocation
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
def validate_well_position(
|
|
430
|
+
self,
|
|
431
|
+
well_location: WellLocations,
|
|
432
|
+
z_offset: float,
|
|
433
|
+
pipette_id: Optional[str] = None,
|
|
434
|
+
) -> None:
|
|
435
|
+
"""Raise exception if operation location is not within well.
|
|
436
|
+
|
|
437
|
+
Primarily this checks if there is not enough liquid in a well to do meniscus-relative static aspiration.
|
|
438
|
+
"""
|
|
439
|
+
if well_location.origin == WellOrigin.MENISCUS:
|
|
440
|
+
assert pipette_id is not None
|
|
441
|
+
lld_min_height = self._pipettes.get_current_tip_lld_settings(
|
|
442
|
+
pipette_id=pipette_id
|
|
443
|
+
)
|
|
444
|
+
if z_offset < lld_min_height:
|
|
445
|
+
if isinstance(well_location, PickUpTipWellLocation):
|
|
446
|
+
raise OperationLocationNotInWellError(
|
|
447
|
+
f"Specifying {well_location.origin} with an offset of {well_location.offset} results in an operation location that could be below the bottom of the well"
|
|
448
|
+
)
|
|
449
|
+
else:
|
|
450
|
+
raise OperationLocationNotInWellError(
|
|
451
|
+
f"Specifying {well_location.origin} with an offset of {well_location.offset} and a volume offset of {well_location.volumeOffset} results in an operation location that could be below the bottom of the well"
|
|
452
|
+
)
|
|
453
|
+
elif z_offset < 0:
|
|
454
|
+
if isinstance(well_location, PickUpTipWellLocation):
|
|
455
|
+
raise OperationLocationNotInWellError(
|
|
456
|
+
f"Specifying {well_location.origin} with an offset of {well_location.offset} results in an operation location below the bottom of the well"
|
|
457
|
+
)
|
|
458
|
+
else:
|
|
459
|
+
raise OperationLocationNotInWellError(
|
|
460
|
+
f"Specifying {well_location.origin} with an offset of {well_location.offset} and a volume offset of {well_location.volumeOffset} results in an operation location below the bottom of the well"
|
|
461
|
+
)
|
|
462
|
+
|
|
408
463
|
def get_well_position(
|
|
409
464
|
self,
|
|
410
465
|
labware_id: str,
|
|
411
466
|
well_name: str,
|
|
412
|
-
well_location: Optional[
|
|
467
|
+
well_location: Optional[WellLocations] = None,
|
|
468
|
+
operation_volume: Optional[float] = None,
|
|
469
|
+
pipette_id: Optional[str] = None,
|
|
413
470
|
) -> Point:
|
|
414
471
|
"""Given relative well location in a labware, get absolute position."""
|
|
415
472
|
labware_pos = self.get_labware_position(labware_id)
|
|
@@ -419,10 +476,17 @@ class GeometryView:
|
|
|
419
476
|
offset = WellOffset(x=0, y=0, z=well_depth)
|
|
420
477
|
if well_location is not None:
|
|
421
478
|
offset = well_location.offset
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
479
|
+
offset_adjustment = self.get_well_offset_adjustment(
|
|
480
|
+
labware_id=labware_id,
|
|
481
|
+
well_name=well_name,
|
|
482
|
+
well_location=well_location,
|
|
483
|
+
well_depth=well_depth,
|
|
484
|
+
operation_volume=operation_volume,
|
|
485
|
+
)
|
|
486
|
+
offset = offset.copy(update={"z": offset.z + offset_adjustment})
|
|
487
|
+
self.validate_well_position(
|
|
488
|
+
well_location=well_location, z_offset=offset.z, pipette_id=pipette_id
|
|
489
|
+
)
|
|
426
490
|
|
|
427
491
|
return Point(
|
|
428
492
|
x=labware_pos.x + offset.x + well_def.x,
|
|
@@ -457,6 +521,41 @@ class GeometryView:
|
|
|
457
521
|
|
|
458
522
|
return WellLocation(offset=WellOffset(x=delta.x, y=delta.y, z=delta.z))
|
|
459
523
|
|
|
524
|
+
def get_relative_liquid_handling_well_location(
|
|
525
|
+
self,
|
|
526
|
+
labware_id: str,
|
|
527
|
+
well_name: str,
|
|
528
|
+
absolute_point: Point,
|
|
529
|
+
is_meniscus: Optional[bool] = None,
|
|
530
|
+
) -> LiquidHandlingWellLocation:
|
|
531
|
+
"""Given absolute position, get relative location of a well in a labware.
|
|
532
|
+
|
|
533
|
+
If is_meniscus is True, absolute_point will hold the z-offset in its z field.
|
|
534
|
+
"""
|
|
535
|
+
if is_meniscus:
|
|
536
|
+
return LiquidHandlingWellLocation(
|
|
537
|
+
origin=WellOrigin.MENISCUS,
|
|
538
|
+
offset=WellOffset(x=0, y=0, z=absolute_point.z),
|
|
539
|
+
)
|
|
540
|
+
else:
|
|
541
|
+
well_absolute_point = self.get_well_position(labware_id, well_name)
|
|
542
|
+
delta = absolute_point - well_absolute_point
|
|
543
|
+
return LiquidHandlingWellLocation(
|
|
544
|
+
offset=WellOffset(x=delta.x, y=delta.y, z=delta.z)
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
def get_relative_pick_up_tip_well_location(
|
|
548
|
+
self,
|
|
549
|
+
labware_id: str,
|
|
550
|
+
well_name: str,
|
|
551
|
+
absolute_point: Point,
|
|
552
|
+
) -> PickUpTipWellLocation:
|
|
553
|
+
"""Given absolute position, get relative location of a well in a labware."""
|
|
554
|
+
well_absolute_point = self.get_well_position(labware_id, well_name)
|
|
555
|
+
delta = absolute_point - well_absolute_point
|
|
556
|
+
|
|
557
|
+
return PickUpTipWellLocation(offset=WellOffset(x=delta.x, y=delta.y, z=delta.z))
|
|
558
|
+
|
|
460
559
|
def get_well_height(
|
|
461
560
|
self,
|
|
462
561
|
labware_id: str,
|
|
@@ -580,6 +679,14 @@ class GeometryView:
|
|
|
580
679
|
),
|
|
581
680
|
)
|
|
582
681
|
|
|
682
|
+
def convert_pick_up_tip_well_location(
|
|
683
|
+
self, well_location: PickUpTipWellLocation
|
|
684
|
+
) -> WellLocation:
|
|
685
|
+
"""Convert PickUpTipWellLocation to WellLocation."""
|
|
686
|
+
return WellLocation(
|
|
687
|
+
origin=WellOrigin(well_location.origin.value), offset=well_location.offset
|
|
688
|
+
)
|
|
689
|
+
|
|
583
690
|
# TODO(jbl 11-30-2023) fold this function into get_ancestor_slot_name see RSS-411
|
|
584
691
|
def _get_staging_slot_name(self, labware_id: str) -> str:
|
|
585
692
|
"""Get the staging slot name that the labware is on."""
|
|
@@ -596,6 +703,12 @@ class GeometryView:
|
|
|
596
703
|
"Cannot get staging slot name for labware not on staging slot."
|
|
597
704
|
)
|
|
598
705
|
|
|
706
|
+
def _get_lid_dock_slot_name(self, labware_id: str) -> str:
|
|
707
|
+
"""Get the staging slot name that the labware is on."""
|
|
708
|
+
labware_location = self._labware.get(labware_id).location
|
|
709
|
+
assert isinstance(labware_location, AddressableAreaLocation)
|
|
710
|
+
return labware_location.addressableAreaName
|
|
711
|
+
|
|
599
712
|
def get_ancestor_slot_name(self, labware_id: str) -> DeckSlotName:
|
|
600
713
|
"""Get the slot name of the labware or the module that the labware is on."""
|
|
601
714
|
labware = self._labware.get(labware_id)
|
|
@@ -613,10 +726,15 @@ class GeometryView:
|
|
|
613
726
|
area_name = labware.location.addressableAreaName
|
|
614
727
|
# TODO we might want to eventually return some sort of staging slot name when we're ready to work through
|
|
615
728
|
# the linting nightmare it will create
|
|
729
|
+
if self._labware.is_absorbance_reader_lid(labware_id):
|
|
730
|
+
raise errors.LocationIsLidDockSlotError(
|
|
731
|
+
"Cannot get ancestor slot name for labware on lid dock slot."
|
|
732
|
+
)
|
|
616
733
|
if fixture_validation.is_staging_slot(area_name):
|
|
617
734
|
raise errors.LocationIsStagingSlotError(
|
|
618
735
|
"Cannot get ancestor slot name for labware on staging slot."
|
|
619
736
|
)
|
|
737
|
+
raise errors.LocationIs
|
|
620
738
|
slot_name = DeckSlotName.from_primitive(area_name)
|
|
621
739
|
elif labware.location == OFF_DECK_LOCATION:
|
|
622
740
|
raise errors.LabwareNotOnDeckError(
|
|
@@ -962,17 +1080,22 @@ class GeometryView:
|
|
|
962
1080
|
from_location: OnDeckLabwareLocation,
|
|
963
1081
|
to_location: OnDeckLabwareLocation,
|
|
964
1082
|
additional_offset_vector: LabwareMovementOffsetData,
|
|
1083
|
+
current_labware: LabwareDefinition,
|
|
965
1084
|
) -> LabwareMovementOffsetData:
|
|
966
1085
|
"""Calculate the final labware offset vector to use in labware movement."""
|
|
967
1086
|
pick_up_offset = (
|
|
968
1087
|
self.get_total_nominal_gripper_offset_for_move_type(
|
|
969
|
-
location=from_location,
|
|
1088
|
+
location=from_location,
|
|
1089
|
+
move_type=_GripperMoveType.PICK_UP_LABWARE,
|
|
1090
|
+
current_labware=current_labware,
|
|
970
1091
|
)
|
|
971
1092
|
+ additional_offset_vector.pickUpOffset
|
|
972
1093
|
)
|
|
973
1094
|
drop_offset = (
|
|
974
1095
|
self.get_total_nominal_gripper_offset_for_move_type(
|
|
975
|
-
location=to_location,
|
|
1096
|
+
location=to_location,
|
|
1097
|
+
move_type=_GripperMoveType.DROP_LABWARE,
|
|
1098
|
+
current_labware=current_labware,
|
|
976
1099
|
)
|
|
977
1100
|
+ additional_offset_vector.dropOffset
|
|
978
1101
|
)
|
|
@@ -1003,7 +1126,10 @@ class GeometryView:
|
|
|
1003
1126
|
return location
|
|
1004
1127
|
|
|
1005
1128
|
def get_total_nominal_gripper_offset_for_move_type(
|
|
1006
|
-
self,
|
|
1129
|
+
self,
|
|
1130
|
+
location: OnDeckLabwareLocation,
|
|
1131
|
+
move_type: _GripperMoveType,
|
|
1132
|
+
current_labware: LabwareDefinition,
|
|
1007
1133
|
) -> LabwareOffsetVector:
|
|
1008
1134
|
"""Get the total of the offsets to be used to pick up labware in its current location."""
|
|
1009
1135
|
if move_type == _GripperMoveType.PICK_UP_LABWARE:
|
|
@@ -1019,14 +1145,39 @@ class GeometryView:
|
|
|
1019
1145
|
location
|
|
1020
1146
|
)
|
|
1021
1147
|
ancestor = self._labware.get_parent_location(location.labwareId)
|
|
1148
|
+
extra_offset = LabwareOffsetVector(x=0, y=0, z=0)
|
|
1149
|
+
if (
|
|
1150
|
+
isinstance(ancestor, ModuleLocation)
|
|
1151
|
+
and self._modules._state.requested_model_by_id[ancestor.moduleId]
|
|
1152
|
+
== ModuleModel.THERMOCYCLER_MODULE_V2
|
|
1153
|
+
and labware_validation.validate_definition_is_lid(current_labware)
|
|
1154
|
+
):
|
|
1155
|
+
if "lidOffsets" in current_labware.gripperOffsets.keys():
|
|
1156
|
+
extra_offset = LabwareOffsetVector(
|
|
1157
|
+
x=current_labware.gripperOffsets[
|
|
1158
|
+
"lidOffsets"
|
|
1159
|
+
].pickUpOffset.x,
|
|
1160
|
+
y=current_labware.gripperOffsets[
|
|
1161
|
+
"lidOffsets"
|
|
1162
|
+
].pickUpOffset.y,
|
|
1163
|
+
z=current_labware.gripperOffsets[
|
|
1164
|
+
"lidOffsets"
|
|
1165
|
+
].pickUpOffset.z,
|
|
1166
|
+
)
|
|
1167
|
+
else:
|
|
1168
|
+
raise errors.LabwareOffsetDoesNotExistError(
|
|
1169
|
+
f"Labware Definition {current_labware.parameters.loadName} does not contain required field 'lidOffsets' of 'gripperOffsets'."
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1022
1172
|
assert isinstance(
|
|
1023
|
-
ancestor, (DeckSlotLocation, ModuleLocation)
|
|
1173
|
+
ancestor, (DeckSlotLocation, ModuleLocation, OnLabwareLocation)
|
|
1024
1174
|
), "No gripper offsets for off-deck labware"
|
|
1025
1175
|
return (
|
|
1026
1176
|
direct_parent_offset.pickUpOffset
|
|
1027
1177
|
+ self._nominal_gripper_offsets_for_location(
|
|
1028
1178
|
location=ancestor
|
|
1029
1179
|
).pickUpOffset
|
|
1180
|
+
+ extra_offset
|
|
1030
1181
|
)
|
|
1031
1182
|
else:
|
|
1032
1183
|
if isinstance(
|
|
@@ -1041,14 +1192,39 @@ class GeometryView:
|
|
|
1041
1192
|
location
|
|
1042
1193
|
)
|
|
1043
1194
|
ancestor = self._labware.get_parent_location(location.labwareId)
|
|
1195
|
+
extra_offset = LabwareOffsetVector(x=0, y=0, z=0)
|
|
1196
|
+
if (
|
|
1197
|
+
isinstance(ancestor, ModuleLocation)
|
|
1198
|
+
and self._modules._state.requested_model_by_id[ancestor.moduleId]
|
|
1199
|
+
== ModuleModel.THERMOCYCLER_MODULE_V2
|
|
1200
|
+
and labware_validation.validate_definition_is_lid(current_labware)
|
|
1201
|
+
):
|
|
1202
|
+
if "lidOffsets" in current_labware.gripperOffsets.keys():
|
|
1203
|
+
extra_offset = LabwareOffsetVector(
|
|
1204
|
+
x=current_labware.gripperOffsets[
|
|
1205
|
+
"lidOffsets"
|
|
1206
|
+
].pickUpOffset.x,
|
|
1207
|
+
y=current_labware.gripperOffsets[
|
|
1208
|
+
"lidOffsets"
|
|
1209
|
+
].pickUpOffset.y,
|
|
1210
|
+
z=current_labware.gripperOffsets[
|
|
1211
|
+
"lidOffsets"
|
|
1212
|
+
].pickUpOffset.z,
|
|
1213
|
+
)
|
|
1214
|
+
else:
|
|
1215
|
+
raise errors.LabwareOffsetDoesNotExistError(
|
|
1216
|
+
f"Labware Definition {current_labware.parameters.loadName} does not contain required field 'lidOffsets' of 'gripperOffsets'."
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1044
1219
|
assert isinstance(
|
|
1045
|
-
ancestor, (DeckSlotLocation, ModuleLocation)
|
|
1220
|
+
ancestor, (DeckSlotLocation, ModuleLocation, OnLabwareLocation)
|
|
1046
1221
|
), "No gripper offsets for off-deck labware"
|
|
1047
1222
|
return (
|
|
1048
1223
|
direct_parent_offset.dropOffset
|
|
1049
1224
|
+ self._nominal_gripper_offsets_for_location(
|
|
1050
1225
|
location=ancestor
|
|
1051
1226
|
).dropOffset
|
|
1227
|
+
+ extra_offset
|
|
1052
1228
|
)
|
|
1053
1229
|
|
|
1054
1230
|
def check_gripper_labware_tip_collision(
|
|
@@ -1112,11 +1288,20 @@ class GeometryView:
|
|
|
1112
1288
|
"""
|
|
1113
1289
|
parent_location = self._labware.get_parent_location(labware_id)
|
|
1114
1290
|
assert isinstance(
|
|
1115
|
-
parent_location,
|
|
1291
|
+
parent_location,
|
|
1292
|
+
(
|
|
1293
|
+
DeckSlotLocation,
|
|
1294
|
+
ModuleLocation,
|
|
1295
|
+
AddressableAreaLocation,
|
|
1296
|
+
),
|
|
1116
1297
|
), "No gripper offsets for off-deck labware"
|
|
1117
1298
|
|
|
1118
1299
|
if isinstance(parent_location, DeckSlotLocation):
|
|
1119
1300
|
slot_name = parent_location.slotName
|
|
1301
|
+
elif isinstance(parent_location, AddressableAreaLocation):
|
|
1302
|
+
slot_name = self._addressable_areas.get_addressable_area_base_slot(
|
|
1303
|
+
parent_location.addressableAreaName
|
|
1304
|
+
)
|
|
1120
1305
|
else:
|
|
1121
1306
|
module_loc = self._modules.get_location(parent_location.moduleId)
|
|
1122
1307
|
slot_name = module_loc.slotName
|
|
@@ -1173,3 +1358,162 @@ class GeometryView:
|
|
|
1173
1358
|
)
|
|
1174
1359
|
|
|
1175
1360
|
return None
|
|
1361
|
+
|
|
1362
|
+
def get_well_offset_adjustment(
|
|
1363
|
+
self,
|
|
1364
|
+
labware_id: str,
|
|
1365
|
+
well_name: str,
|
|
1366
|
+
well_location: WellLocations,
|
|
1367
|
+
well_depth: float,
|
|
1368
|
+
operation_volume: Optional[float] = None,
|
|
1369
|
+
) -> float:
|
|
1370
|
+
"""Return a z-axis distance that accounts for well handling height and operation volume.
|
|
1371
|
+
|
|
1372
|
+
Distance is with reference to the well bottom.
|
|
1373
|
+
"""
|
|
1374
|
+
# TODO(pbm, 10-23-24): refactor to smartly reduce height/volume conversions
|
|
1375
|
+
initial_handling_height = self.get_well_handling_height(
|
|
1376
|
+
labware_id=labware_id,
|
|
1377
|
+
well_name=well_name,
|
|
1378
|
+
well_location=well_location,
|
|
1379
|
+
well_depth=well_depth,
|
|
1380
|
+
)
|
|
1381
|
+
if isinstance(well_location, PickUpTipWellLocation):
|
|
1382
|
+
volume = 0.0
|
|
1383
|
+
elif isinstance(well_location.volumeOffset, float):
|
|
1384
|
+
volume = well_location.volumeOffset
|
|
1385
|
+
elif well_location.volumeOffset == "operationVolume":
|
|
1386
|
+
volume = operation_volume or 0.0
|
|
1387
|
+
|
|
1388
|
+
if volume:
|
|
1389
|
+
return self.get_well_height_after_volume(
|
|
1390
|
+
labware_id=labware_id,
|
|
1391
|
+
well_name=well_name,
|
|
1392
|
+
initial_height=initial_handling_height,
|
|
1393
|
+
volume=volume,
|
|
1394
|
+
)
|
|
1395
|
+
else:
|
|
1396
|
+
return initial_handling_height
|
|
1397
|
+
|
|
1398
|
+
def get_meniscus_height(
|
|
1399
|
+
self,
|
|
1400
|
+
labware_id: str,
|
|
1401
|
+
well_name: str,
|
|
1402
|
+
) -> float:
|
|
1403
|
+
"""Returns stored meniscus height in specified well."""
|
|
1404
|
+
well_liquid = self._wells.get_well_liquid_info(
|
|
1405
|
+
labware_id=labware_id, well_name=well_name
|
|
1406
|
+
)
|
|
1407
|
+
if (
|
|
1408
|
+
well_liquid.probed_height is not None
|
|
1409
|
+
and well_liquid.probed_height.height is not None
|
|
1410
|
+
):
|
|
1411
|
+
return well_liquid.probed_height.height
|
|
1412
|
+
elif (
|
|
1413
|
+
well_liquid.loaded_volume is not None
|
|
1414
|
+
and well_liquid.loaded_volume.volume is not None
|
|
1415
|
+
):
|
|
1416
|
+
return self.get_well_height_at_volume(
|
|
1417
|
+
labware_id=labware_id,
|
|
1418
|
+
well_name=well_name,
|
|
1419
|
+
volume=well_liquid.loaded_volume.volume,
|
|
1420
|
+
)
|
|
1421
|
+
elif (
|
|
1422
|
+
well_liquid.probed_volume is not None
|
|
1423
|
+
and well_liquid.probed_volume.volume is not None
|
|
1424
|
+
):
|
|
1425
|
+
return self.get_well_height_at_volume(
|
|
1426
|
+
labware_id=labware_id,
|
|
1427
|
+
well_name=well_name,
|
|
1428
|
+
volume=well_liquid.probed_volume.volume,
|
|
1429
|
+
)
|
|
1430
|
+
else:
|
|
1431
|
+
raise errors.LiquidHeightUnknownError(
|
|
1432
|
+
"Must LiquidProbe or LoadLiquid before specifying WellOrigin.MENISCUS."
|
|
1433
|
+
)
|
|
1434
|
+
|
|
1435
|
+
def get_well_handling_height(
|
|
1436
|
+
self,
|
|
1437
|
+
labware_id: str,
|
|
1438
|
+
well_name: str,
|
|
1439
|
+
well_location: WellLocations,
|
|
1440
|
+
well_depth: float,
|
|
1441
|
+
) -> float:
|
|
1442
|
+
"""Return the handling height for a labware well (with reference to the well bottom)."""
|
|
1443
|
+
handling_height = 0.0
|
|
1444
|
+
if well_location.origin == WellOrigin.TOP:
|
|
1445
|
+
handling_height = well_depth
|
|
1446
|
+
elif well_location.origin == WellOrigin.CENTER:
|
|
1447
|
+
handling_height = well_depth / 2.0
|
|
1448
|
+
elif well_location.origin == WellOrigin.MENISCUS:
|
|
1449
|
+
handling_height = self.get_meniscus_height(
|
|
1450
|
+
labware_id=labware_id, well_name=well_name
|
|
1451
|
+
)
|
|
1452
|
+
return float(handling_height)
|
|
1453
|
+
|
|
1454
|
+
def get_well_height_after_volume(
|
|
1455
|
+
self, labware_id: str, well_name: str, initial_height: float, volume: float
|
|
1456
|
+
) -> float:
|
|
1457
|
+
"""Return the height of liquid in a labware well after a given volume has been handled.
|
|
1458
|
+
|
|
1459
|
+
This is given an initial handling height, with reference to the well bottom.
|
|
1460
|
+
"""
|
|
1461
|
+
well_geometry = self._labware.get_well_geometry(
|
|
1462
|
+
labware_id=labware_id, well_name=well_name
|
|
1463
|
+
)
|
|
1464
|
+
initial_volume = find_volume_at_well_height(
|
|
1465
|
+
target_height=initial_height, well_geometry=well_geometry
|
|
1466
|
+
)
|
|
1467
|
+
final_volume = initial_volume + volume
|
|
1468
|
+
return find_height_at_well_volume(
|
|
1469
|
+
target_volume=final_volume, well_geometry=well_geometry
|
|
1470
|
+
)
|
|
1471
|
+
|
|
1472
|
+
def get_well_height_at_volume(
|
|
1473
|
+
self, labware_id: str, well_name: str, volume: float
|
|
1474
|
+
) -> float:
|
|
1475
|
+
"""Convert well volume to height."""
|
|
1476
|
+
well_geometry = self._labware.get_well_geometry(labware_id, well_name)
|
|
1477
|
+
return find_height_at_well_volume(
|
|
1478
|
+
target_volume=volume, well_geometry=well_geometry
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
def get_well_volume_at_height(
|
|
1482
|
+
self, labware_id: str, well_name: str, height: float
|
|
1483
|
+
) -> float:
|
|
1484
|
+
"""Convert well height to volume."""
|
|
1485
|
+
well_geometry = self._labware.get_well_geometry(labware_id, well_name)
|
|
1486
|
+
return find_volume_at_well_height(
|
|
1487
|
+
target_height=height, well_geometry=well_geometry
|
|
1488
|
+
)
|
|
1489
|
+
|
|
1490
|
+
def validate_dispense_volume_into_well(
|
|
1491
|
+
self,
|
|
1492
|
+
labware_id: str,
|
|
1493
|
+
well_name: str,
|
|
1494
|
+
well_location: WellLocations,
|
|
1495
|
+
volume: float,
|
|
1496
|
+
) -> None:
|
|
1497
|
+
"""Raise InvalidDispenseVolumeError if planned dispense volume will overflow well."""
|
|
1498
|
+
well_def = self._labware.get_well_definition(labware_id, well_name)
|
|
1499
|
+
well_volumetric_capacity = well_def.totalLiquidVolume
|
|
1500
|
+
if well_location.origin == WellOrigin.MENISCUS:
|
|
1501
|
+
# TODO(pbm, 10-23-24): refactor to smartly reduce height/volume conversions
|
|
1502
|
+
well_geometry = self._labware.get_well_geometry(labware_id, well_name)
|
|
1503
|
+
meniscus_height = self.get_meniscus_height(
|
|
1504
|
+
labware_id=labware_id, well_name=well_name
|
|
1505
|
+
)
|
|
1506
|
+
meniscus_volume = find_volume_at_well_height(
|
|
1507
|
+
target_height=meniscus_height, well_geometry=well_geometry
|
|
1508
|
+
)
|
|
1509
|
+
remaining_volume = well_volumetric_capacity - meniscus_volume
|
|
1510
|
+
if volume > remaining_volume:
|
|
1511
|
+
raise errors.InvalidDispenseVolumeError(
|
|
1512
|
+
f"Attempting to dispense {volume}µL of liquid into a well that can currently only hold {remaining_volume}µL (well {well_name} in labware_id: {labware_id})"
|
|
1513
|
+
)
|
|
1514
|
+
else:
|
|
1515
|
+
# TODO(pbm, 10-08-24): factor in well (LabwareStore) state volume
|
|
1516
|
+
if volume > well_volumetric_capacity:
|
|
1517
|
+
raise errors.InvalidDispenseVolumeError(
|
|
1518
|
+
f"Attempting to dispense {volume}µL of liquid into a well that can only hold {well_volumetric_capacity}µL (well {well_name} in labware_id: {labware_id})"
|
|
1519
|
+
)
|