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.
- 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
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Structures to represent changes that commands want to make to engine state."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import dataclasses
|
|
5
|
+
import enum
|
|
6
|
+
import typing
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
from opentrons.hardware_control.nozzle_manager import NozzleMap
|
|
10
|
+
from opentrons.protocol_engine.resources import pipette_data_provider
|
|
11
|
+
from opentrons.protocol_engine.types import DeckPoint, LabwareLocation, TipGeometry
|
|
12
|
+
from opentrons.types import MountType
|
|
13
|
+
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
14
|
+
from opentrons_shared_data.pipette.types import PipetteNameType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _NoChangeEnum(enum.Enum):
|
|
18
|
+
NO_CHANGE = enum.auto()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
NO_CHANGE: typing.Final = _NoChangeEnum.NO_CHANGE
|
|
22
|
+
"""A sentinel value to indicate that a value shouldn't be changed.
|
|
23
|
+
|
|
24
|
+
Useful when `None` is semantically unclear or already has some other meaning.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
NoChangeType: typing.TypeAlias = typing.Literal[_NoChangeEnum.NO_CHANGE]
|
|
29
|
+
"""The type of `NO_CHANGE`, as `NoneType` is to `None`.
|
|
30
|
+
|
|
31
|
+
Unfortunately, mypy doesn't let us write `Literal[NO_CHANGE]`. Use this instead.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _ClearEnum(enum.Enum):
|
|
36
|
+
CLEAR = enum.auto()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
CLEAR: typing.Final = _ClearEnum.CLEAR
|
|
40
|
+
"""A sentinel value to indicate that a value should be cleared.
|
|
41
|
+
|
|
42
|
+
Useful when `None` is semantically unclear or has some other meaning.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
ClearType: typing.TypeAlias = typing.Literal[_ClearEnum.CLEAR]
|
|
47
|
+
"""The type of `CLEAR`, as `NoneType` is to `None`.
|
|
48
|
+
|
|
49
|
+
Unfortunately, mypy doesn't let us write `Literal[CLEAR]`. Use this instead.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclasses.dataclass(frozen=True)
|
|
54
|
+
class Well:
|
|
55
|
+
"""Designates a well in a labware."""
|
|
56
|
+
|
|
57
|
+
labware_id: str
|
|
58
|
+
well_name: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclasses.dataclass(frozen=True)
|
|
62
|
+
class AddressableArea:
|
|
63
|
+
"""Designates an addressable area."""
|
|
64
|
+
|
|
65
|
+
addressable_area_name: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclasses.dataclass
|
|
69
|
+
class PipetteLocationUpdate:
|
|
70
|
+
"""An update to a pipette's location."""
|
|
71
|
+
|
|
72
|
+
pipette_id: str
|
|
73
|
+
"""The ID of the already-loaded pipette."""
|
|
74
|
+
|
|
75
|
+
new_location: Well | AddressableArea | None | NoChangeType
|
|
76
|
+
"""The pipette's new logical location.
|
|
77
|
+
|
|
78
|
+
Note: `new_location=None` means "change the location to `None` (unknown)",
|
|
79
|
+
not "do not change the location".
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
new_deck_point: DeckPoint | NoChangeType
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclasses.dataclass
|
|
86
|
+
class LabwareLocationUpdate:
|
|
87
|
+
"""An update to a labware's location."""
|
|
88
|
+
|
|
89
|
+
labware_id: str
|
|
90
|
+
"""The ID of the already-loaded labware."""
|
|
91
|
+
|
|
92
|
+
new_location: LabwareLocation
|
|
93
|
+
"""The labware's new location."""
|
|
94
|
+
|
|
95
|
+
offset_id: typing.Optional[str]
|
|
96
|
+
"""The ID of the labware's new offset, for its new location."""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclasses.dataclass
|
|
100
|
+
class LoadedLabwareUpdate:
|
|
101
|
+
"""An update that loads a new labware."""
|
|
102
|
+
|
|
103
|
+
labware_id: str
|
|
104
|
+
"""The unique ID of the new labware."""
|
|
105
|
+
|
|
106
|
+
new_location: LabwareLocation
|
|
107
|
+
"""The labware's initial location."""
|
|
108
|
+
|
|
109
|
+
offset_id: typing.Optional[str]
|
|
110
|
+
"""The ID of the labware's offset."""
|
|
111
|
+
|
|
112
|
+
display_name: typing.Optional[str]
|
|
113
|
+
|
|
114
|
+
definition: LabwareDefinition
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclasses.dataclass
|
|
118
|
+
class LoadPipetteUpdate:
|
|
119
|
+
"""An update that loads a new pipette.
|
|
120
|
+
|
|
121
|
+
NOTE: Currently, if this is provided, a PipetteConfigUpdate must always be
|
|
122
|
+
provided alongside it to fully initialize everything.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
pipette_id: str
|
|
126
|
+
"""The unique ID of the new pipette."""
|
|
127
|
+
|
|
128
|
+
pipette_name: PipetteNameType
|
|
129
|
+
mount: MountType
|
|
130
|
+
liquid_presence_detection: typing.Optional[bool]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclasses.dataclass
|
|
134
|
+
class PipetteConfigUpdate:
|
|
135
|
+
"""An update to a pipette's config."""
|
|
136
|
+
|
|
137
|
+
pipette_id: str
|
|
138
|
+
"""The ID of the already-loaded pipette."""
|
|
139
|
+
|
|
140
|
+
# todo(mm, 2024-10-14): Does serial_number belong in LoadPipetteUpdate?
|
|
141
|
+
serial_number: str
|
|
142
|
+
|
|
143
|
+
config: pipette_data_provider.LoadedStaticPipetteData
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclasses.dataclass
|
|
147
|
+
class PipetteNozzleMapUpdate:
|
|
148
|
+
"""Update pipette nozzle map."""
|
|
149
|
+
|
|
150
|
+
pipette_id: str
|
|
151
|
+
nozzle_map: NozzleMap
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclasses.dataclass
|
|
155
|
+
class PipetteTipStateUpdate:
|
|
156
|
+
"""Update pipette tip state."""
|
|
157
|
+
|
|
158
|
+
pipette_id: str
|
|
159
|
+
tip_geometry: typing.Optional[TipGeometry]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclasses.dataclass
|
|
163
|
+
class TipsUsedUpdate:
|
|
164
|
+
"""Represents an update that marks tips in a tip rack as used."""
|
|
165
|
+
|
|
166
|
+
pipette_id: str
|
|
167
|
+
"""The pipette that did the tip pickup."""
|
|
168
|
+
|
|
169
|
+
labware_id: str
|
|
170
|
+
|
|
171
|
+
well_name: str
|
|
172
|
+
"""The well that the pipette's primary nozzle targeted.
|
|
173
|
+
|
|
174
|
+
Wells in addition to this one will also be marked as used, depending on the
|
|
175
|
+
pipette's nozzle layout.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclasses.dataclass
|
|
180
|
+
class LiquidLoadedUpdate:
|
|
181
|
+
"""An update from loading a liquid."""
|
|
182
|
+
|
|
183
|
+
labware_id: str
|
|
184
|
+
volumes: typing.Dict[str, float]
|
|
185
|
+
last_loaded: datetime
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclasses.dataclass
|
|
189
|
+
class LiquidProbedUpdate:
|
|
190
|
+
"""An update from probing a liquid."""
|
|
191
|
+
|
|
192
|
+
labware_id: str
|
|
193
|
+
well_name: str
|
|
194
|
+
last_probed: datetime
|
|
195
|
+
height: float | ClearType
|
|
196
|
+
volume: float | ClearType
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclasses.dataclass
|
|
200
|
+
class LiquidOperatedUpdate:
|
|
201
|
+
"""An update from operating a liquid."""
|
|
202
|
+
|
|
203
|
+
labware_id: str
|
|
204
|
+
well_name: str
|
|
205
|
+
volume_added: float | ClearType
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@dataclasses.dataclass
|
|
209
|
+
class StateUpdate:
|
|
210
|
+
"""Represents an update to perform on engine state."""
|
|
211
|
+
|
|
212
|
+
pipette_location: PipetteLocationUpdate | NoChangeType | ClearType = NO_CHANGE
|
|
213
|
+
|
|
214
|
+
loaded_pipette: LoadPipetteUpdate | NoChangeType = NO_CHANGE
|
|
215
|
+
|
|
216
|
+
pipette_config: PipetteConfigUpdate | NoChangeType = NO_CHANGE
|
|
217
|
+
|
|
218
|
+
pipette_nozzle_map: PipetteNozzleMapUpdate | NoChangeType = NO_CHANGE
|
|
219
|
+
|
|
220
|
+
pipette_tip_state: PipetteTipStateUpdate | NoChangeType = NO_CHANGE
|
|
221
|
+
|
|
222
|
+
labware_location: LabwareLocationUpdate | NoChangeType = NO_CHANGE
|
|
223
|
+
|
|
224
|
+
loaded_labware: LoadedLabwareUpdate | NoChangeType = NO_CHANGE
|
|
225
|
+
|
|
226
|
+
tips_used: TipsUsedUpdate | NoChangeType = NO_CHANGE
|
|
227
|
+
|
|
228
|
+
liquid_loaded: LiquidLoadedUpdate | NoChangeType = NO_CHANGE
|
|
229
|
+
|
|
230
|
+
liquid_probed: LiquidProbedUpdate | NoChangeType = NO_CHANGE
|
|
231
|
+
|
|
232
|
+
liquid_operated: LiquidOperatedUpdate | NoChangeType = NO_CHANGE
|
|
233
|
+
|
|
234
|
+
# These convenience functions let the caller avoid the boilerplate of constructing a
|
|
235
|
+
# complicated dataclass tree.
|
|
236
|
+
|
|
237
|
+
@typing.overload
|
|
238
|
+
def set_pipette_location(
|
|
239
|
+
self,
|
|
240
|
+
*,
|
|
241
|
+
pipette_id: str,
|
|
242
|
+
new_labware_id: str,
|
|
243
|
+
new_well_name: str,
|
|
244
|
+
new_deck_point: DeckPoint,
|
|
245
|
+
) -> None:
|
|
246
|
+
"""Schedule a pipette's location to be set to a well."""
|
|
247
|
+
|
|
248
|
+
@typing.overload
|
|
249
|
+
def set_pipette_location(
|
|
250
|
+
self,
|
|
251
|
+
*,
|
|
252
|
+
pipette_id: str,
|
|
253
|
+
new_addressable_area_name: str,
|
|
254
|
+
new_deck_point: DeckPoint,
|
|
255
|
+
) -> None:
|
|
256
|
+
"""Schedule a pipette's location to be set to an addressable area."""
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
def set_pipette_location( # noqa: D102
|
|
260
|
+
self,
|
|
261
|
+
*,
|
|
262
|
+
pipette_id: str,
|
|
263
|
+
new_labware_id: str | NoChangeType = NO_CHANGE,
|
|
264
|
+
new_well_name: str | NoChangeType = NO_CHANGE,
|
|
265
|
+
new_addressable_area_name: str | NoChangeType = NO_CHANGE,
|
|
266
|
+
new_deck_point: DeckPoint,
|
|
267
|
+
) -> None:
|
|
268
|
+
if new_addressable_area_name != NO_CHANGE:
|
|
269
|
+
self.pipette_location = PipetteLocationUpdate(
|
|
270
|
+
pipette_id=pipette_id,
|
|
271
|
+
new_location=AddressableArea(
|
|
272
|
+
addressable_area_name=new_addressable_area_name
|
|
273
|
+
),
|
|
274
|
+
new_deck_point=new_deck_point,
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
# These asserts should always pass because of the overloads.
|
|
278
|
+
assert new_labware_id != NO_CHANGE
|
|
279
|
+
assert new_well_name != NO_CHANGE
|
|
280
|
+
|
|
281
|
+
self.pipette_location = PipetteLocationUpdate(
|
|
282
|
+
pipette_id=pipette_id,
|
|
283
|
+
new_location=Well(labware_id=new_labware_id, well_name=new_well_name),
|
|
284
|
+
new_deck_point=new_deck_point,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def clear_all_pipette_locations(self) -> None:
|
|
288
|
+
"""Mark all pipettes as having an unknown location."""
|
|
289
|
+
self.pipette_location = CLEAR
|
|
290
|
+
|
|
291
|
+
def set_labware_location(
|
|
292
|
+
self,
|
|
293
|
+
*,
|
|
294
|
+
labware_id: str,
|
|
295
|
+
new_location: LabwareLocation,
|
|
296
|
+
new_offset_id: str | None,
|
|
297
|
+
) -> None:
|
|
298
|
+
"""Set a labware's location. See `LabwareLocationUpdate`."""
|
|
299
|
+
self.labware_location = LabwareLocationUpdate(
|
|
300
|
+
labware_id=labware_id,
|
|
301
|
+
new_location=new_location,
|
|
302
|
+
offset_id=new_offset_id,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def set_loaded_labware(
|
|
306
|
+
self,
|
|
307
|
+
definition: LabwareDefinition,
|
|
308
|
+
labware_id: str,
|
|
309
|
+
offset_id: typing.Optional[str],
|
|
310
|
+
display_name: typing.Optional[str],
|
|
311
|
+
location: LabwareLocation,
|
|
312
|
+
) -> None:
|
|
313
|
+
"""Add a new labware to state. See `LoadedLabwareUpdate`."""
|
|
314
|
+
self.loaded_labware = LoadedLabwareUpdate(
|
|
315
|
+
definition=definition,
|
|
316
|
+
labware_id=labware_id,
|
|
317
|
+
offset_id=offset_id,
|
|
318
|
+
new_location=location,
|
|
319
|
+
display_name=display_name,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def set_load_pipette(
|
|
323
|
+
self,
|
|
324
|
+
pipette_id: str,
|
|
325
|
+
pipette_name: PipetteNameType,
|
|
326
|
+
mount: MountType,
|
|
327
|
+
liquid_presence_detection: typing.Optional[bool],
|
|
328
|
+
) -> None:
|
|
329
|
+
"""Add a new pipette to state. See `LoadPipetteUpdate`."""
|
|
330
|
+
self.loaded_pipette = LoadPipetteUpdate(
|
|
331
|
+
pipette_id=pipette_id,
|
|
332
|
+
pipette_name=pipette_name,
|
|
333
|
+
mount=mount,
|
|
334
|
+
liquid_presence_detection=liquid_presence_detection,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def update_pipette_config(
|
|
338
|
+
self,
|
|
339
|
+
pipette_id: str,
|
|
340
|
+
config: pipette_data_provider.LoadedStaticPipetteData,
|
|
341
|
+
serial_number: str,
|
|
342
|
+
) -> None:
|
|
343
|
+
"""Update a pipette's config. See `PipetteConfigUpdate`."""
|
|
344
|
+
self.pipette_config = PipetteConfigUpdate(
|
|
345
|
+
pipette_id=pipette_id, config=config, serial_number=serial_number
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
def update_pipette_nozzle(self, pipette_id: str, nozzle_map: NozzleMap) -> None:
|
|
349
|
+
"""Update a pipette's nozzle map. See `PipetteNozzleMapUpdate`."""
|
|
350
|
+
self.pipette_nozzle_map = PipetteNozzleMapUpdate(
|
|
351
|
+
pipette_id=pipette_id, nozzle_map=nozzle_map
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
def update_pipette_tip_state(
|
|
355
|
+
self, pipette_id: str, tip_geometry: typing.Optional[TipGeometry]
|
|
356
|
+
) -> None:
|
|
357
|
+
"""Update a pipette's tip state. See `PipetteTipStateUpdate`."""
|
|
358
|
+
self.pipette_tip_state = PipetteTipStateUpdate(
|
|
359
|
+
pipette_id=pipette_id, tip_geometry=tip_geometry
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
def mark_tips_as_used(
|
|
363
|
+
self, pipette_id: str, labware_id: str, well_name: str
|
|
364
|
+
) -> None:
|
|
365
|
+
"""Mark tips in a tip rack as used. See `TipsUsedUpdate`."""
|
|
366
|
+
self.tips_used = TipsUsedUpdate(
|
|
367
|
+
pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def set_liquid_loaded(
|
|
371
|
+
self,
|
|
372
|
+
labware_id: str,
|
|
373
|
+
volumes: typing.Dict[str, float],
|
|
374
|
+
last_loaded: datetime,
|
|
375
|
+
) -> None:
|
|
376
|
+
"""Add liquid volumes to well state. See `LoadLiquidUpdate`."""
|
|
377
|
+
self.liquid_loaded = LiquidLoadedUpdate(
|
|
378
|
+
labware_id=labware_id,
|
|
379
|
+
volumes=volumes,
|
|
380
|
+
last_loaded=last_loaded,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def set_liquid_probed(
|
|
384
|
+
self,
|
|
385
|
+
labware_id: str,
|
|
386
|
+
well_name: str,
|
|
387
|
+
last_probed: datetime,
|
|
388
|
+
height: float | ClearType,
|
|
389
|
+
volume: float | ClearType,
|
|
390
|
+
) -> None:
|
|
391
|
+
"""Add a liquid height and volume to well state. See `ProbeLiquidUpdate`."""
|
|
392
|
+
self.liquid_probed = LiquidProbedUpdate(
|
|
393
|
+
labware_id=labware_id,
|
|
394
|
+
well_name=well_name,
|
|
395
|
+
height=height,
|
|
396
|
+
volume=volume,
|
|
397
|
+
last_probed=last_probed,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
def set_liquid_operated(
|
|
401
|
+
self, labware_id: str, well_name: str, volume_added: float | ClearType
|
|
402
|
+
) -> None:
|
|
403
|
+
"""Update liquid volumes in well state. See `OperateLiquidUpdate`."""
|
|
404
|
+
self.liquid_operated = LiquidOperatedUpdate(
|
|
405
|
+
labware_id=labware_id,
|
|
406
|
+
well_name=well_name,
|
|
407
|
+
volume_added=volume_added,
|
|
408
|
+
)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Basic well data state and store."""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Dict, List, Union, Iterator, Optional, Tuple, overload, TypeVar
|
|
4
|
+
|
|
5
|
+
from opentrons.protocol_engine.types import (
|
|
6
|
+
ProbedHeightInfo,
|
|
7
|
+
ProbedVolumeInfo,
|
|
8
|
+
LoadedVolumeInfo,
|
|
9
|
+
WellInfoSummary,
|
|
10
|
+
WellLiquidInfo,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from . import update_types
|
|
14
|
+
from ._abstract_store import HasState, HandlesActions
|
|
15
|
+
from ..actions import Action
|
|
16
|
+
from ..actions.get_state_update import get_state_updates
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
LabwareId = str
|
|
20
|
+
WellName = str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class WellState:
|
|
25
|
+
"""State of all wells."""
|
|
26
|
+
|
|
27
|
+
loaded_volumes: Dict[LabwareId, Dict[WellName, LoadedVolumeInfo]]
|
|
28
|
+
probed_heights: Dict[LabwareId, Dict[WellName, ProbedHeightInfo]]
|
|
29
|
+
probed_volumes: Dict[LabwareId, Dict[WellName, ProbedVolumeInfo]]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WellStore(HasState[WellState], HandlesActions):
|
|
33
|
+
"""Well state container."""
|
|
34
|
+
|
|
35
|
+
_state: WellState
|
|
36
|
+
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
"""Initialize a well store and its state."""
|
|
39
|
+
self._state = WellState(loaded_volumes={}, probed_heights={}, probed_volumes={})
|
|
40
|
+
|
|
41
|
+
def handle_action(self, action: Action) -> None:
|
|
42
|
+
"""Modify state in reaction to an action."""
|
|
43
|
+
for state_update in get_state_updates(action):
|
|
44
|
+
if state_update.liquid_loaded != update_types.NO_CHANGE:
|
|
45
|
+
self._handle_liquid_loaded_update(state_update.liquid_loaded)
|
|
46
|
+
if state_update.liquid_probed != update_types.NO_CHANGE:
|
|
47
|
+
self._handle_liquid_probed_update(state_update.liquid_probed)
|
|
48
|
+
if state_update.liquid_operated != update_types.NO_CHANGE:
|
|
49
|
+
self._handle_liquid_operated_update(state_update.liquid_operated)
|
|
50
|
+
|
|
51
|
+
def _handle_liquid_loaded_update(
|
|
52
|
+
self, state_update: update_types.LiquidLoadedUpdate
|
|
53
|
+
) -> None:
|
|
54
|
+
labware_id = state_update.labware_id
|
|
55
|
+
if labware_id not in self._state.loaded_volumes:
|
|
56
|
+
self._state.loaded_volumes[labware_id] = {}
|
|
57
|
+
for (well, volume) in state_update.volumes.items():
|
|
58
|
+
self._state.loaded_volumes[labware_id][well] = LoadedVolumeInfo(
|
|
59
|
+
volume=_none_from_clear(volume),
|
|
60
|
+
last_loaded=state_update.last_loaded,
|
|
61
|
+
operations_since_load=0,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def _handle_liquid_probed_update(
|
|
65
|
+
self, state_update: update_types.LiquidProbedUpdate
|
|
66
|
+
) -> None:
|
|
67
|
+
labware_id = state_update.labware_id
|
|
68
|
+
well_name = state_update.well_name
|
|
69
|
+
if labware_id not in self._state.probed_heights:
|
|
70
|
+
self._state.probed_heights[labware_id] = {}
|
|
71
|
+
if labware_id not in self._state.probed_volumes:
|
|
72
|
+
self._state.probed_volumes[labware_id] = {}
|
|
73
|
+
self._state.probed_heights[labware_id][well_name] = ProbedHeightInfo(
|
|
74
|
+
height=_none_from_clear(state_update.height),
|
|
75
|
+
last_probed=state_update.last_probed,
|
|
76
|
+
)
|
|
77
|
+
self._state.probed_volumes[labware_id][well_name] = ProbedVolumeInfo(
|
|
78
|
+
volume=_none_from_clear(state_update.volume),
|
|
79
|
+
last_probed=state_update.last_probed,
|
|
80
|
+
operations_since_probe=0,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def _handle_liquid_operated_update(
|
|
84
|
+
self, state_update: update_types.LiquidOperatedUpdate
|
|
85
|
+
) -> None:
|
|
86
|
+
labware_id = state_update.labware_id
|
|
87
|
+
well_name = state_update.well_name
|
|
88
|
+
if (
|
|
89
|
+
labware_id in self._state.loaded_volumes
|
|
90
|
+
and well_name in self._state.loaded_volumes[labware_id]
|
|
91
|
+
):
|
|
92
|
+
if state_update.volume_added is update_types.CLEAR:
|
|
93
|
+
del self._state.loaded_volumes[labware_id][well_name]
|
|
94
|
+
else:
|
|
95
|
+
prev_loaded_vol_info = self._state.loaded_volumes[labware_id][well_name]
|
|
96
|
+
assert prev_loaded_vol_info.volume is not None
|
|
97
|
+
self._state.loaded_volumes[labware_id][well_name] = LoadedVolumeInfo(
|
|
98
|
+
volume=prev_loaded_vol_info.volume + state_update.volume_added,
|
|
99
|
+
last_loaded=prev_loaded_vol_info.last_loaded,
|
|
100
|
+
operations_since_load=prev_loaded_vol_info.operations_since_load
|
|
101
|
+
+ 1,
|
|
102
|
+
)
|
|
103
|
+
if (
|
|
104
|
+
labware_id in self._state.probed_heights
|
|
105
|
+
and well_name in self._state.probed_heights[labware_id]
|
|
106
|
+
):
|
|
107
|
+
del self._state.probed_heights[labware_id][well_name]
|
|
108
|
+
if (
|
|
109
|
+
labware_id in self._state.probed_volumes
|
|
110
|
+
and well_name in self._state.probed_volumes[labware_id]
|
|
111
|
+
):
|
|
112
|
+
if state_update.volume_added is update_types.CLEAR:
|
|
113
|
+
del self._state.probed_volumes[labware_id][well_name]
|
|
114
|
+
else:
|
|
115
|
+
prev_probed_vol_info = self._state.probed_volumes[labware_id][well_name]
|
|
116
|
+
if prev_probed_vol_info.volume is None:
|
|
117
|
+
new_vol_info: float | None = None
|
|
118
|
+
else:
|
|
119
|
+
new_vol_info = (
|
|
120
|
+
prev_probed_vol_info.volume + state_update.volume_added
|
|
121
|
+
)
|
|
122
|
+
self._state.probed_volumes[labware_id][well_name] = ProbedVolumeInfo(
|
|
123
|
+
volume=new_vol_info,
|
|
124
|
+
last_probed=prev_probed_vol_info.last_probed,
|
|
125
|
+
operations_since_probe=prev_probed_vol_info.operations_since_probe
|
|
126
|
+
+ 1,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class WellView(HasState[WellState]):
|
|
131
|
+
"""Read-only well state view."""
|
|
132
|
+
|
|
133
|
+
_state: WellState
|
|
134
|
+
|
|
135
|
+
def __init__(self, state: WellState) -> None:
|
|
136
|
+
"""Initialize the computed view of well state.
|
|
137
|
+
|
|
138
|
+
Arguments:
|
|
139
|
+
state: Well state dataclass used for all calculations.
|
|
140
|
+
"""
|
|
141
|
+
self._state = state
|
|
142
|
+
|
|
143
|
+
def get_well_liquid_info(self, labware_id: str, well_name: str) -> WellLiquidInfo:
|
|
144
|
+
"""Return all the liquid info for a well."""
|
|
145
|
+
if (
|
|
146
|
+
labware_id not in self._state.loaded_volumes
|
|
147
|
+
or well_name not in self._state.loaded_volumes[labware_id]
|
|
148
|
+
):
|
|
149
|
+
loaded_volume_info = None
|
|
150
|
+
else:
|
|
151
|
+
loaded_volume_info = self._state.loaded_volumes[labware_id][well_name]
|
|
152
|
+
if (
|
|
153
|
+
labware_id not in self._state.probed_heights
|
|
154
|
+
or well_name not in self._state.probed_heights[labware_id]
|
|
155
|
+
):
|
|
156
|
+
probed_height_info = None
|
|
157
|
+
else:
|
|
158
|
+
probed_height_info = self._state.probed_heights[labware_id][well_name]
|
|
159
|
+
if (
|
|
160
|
+
labware_id not in self._state.probed_volumes
|
|
161
|
+
or well_name not in self._state.probed_volumes[labware_id]
|
|
162
|
+
):
|
|
163
|
+
probed_volume_info = None
|
|
164
|
+
else:
|
|
165
|
+
probed_volume_info = self._state.probed_volumes[labware_id][well_name]
|
|
166
|
+
return WellLiquidInfo(
|
|
167
|
+
loaded_volume=loaded_volume_info,
|
|
168
|
+
probed_height=probed_height_info,
|
|
169
|
+
probed_volume=probed_volume_info,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def get_all(self) -> List[WellInfoSummary]:
|
|
173
|
+
"""Get all well liquid info summaries."""
|
|
174
|
+
|
|
175
|
+
def _all_well_combos() -> Iterator[Tuple[str, str, str]]:
|
|
176
|
+
for labware, lv_wells in self._state.loaded_volumes.items():
|
|
177
|
+
for well_name in lv_wells.keys():
|
|
178
|
+
yield f"{labware}{well_name}", labware, well_name
|
|
179
|
+
for labware, ph_wells in self._state.probed_heights.items():
|
|
180
|
+
for well_name in ph_wells.keys():
|
|
181
|
+
yield f"{labware}{well_name}", labware, well_name
|
|
182
|
+
for labware, pv_wells in self._state.probed_volumes.items():
|
|
183
|
+
for well_name in pv_wells.keys():
|
|
184
|
+
yield f"{labware}{well_name}", labware, well_name
|
|
185
|
+
|
|
186
|
+
wells = {
|
|
187
|
+
key: (labware_id, well_name)
|
|
188
|
+
for key, labware_id, well_name in _all_well_combos()
|
|
189
|
+
}
|
|
190
|
+
return [
|
|
191
|
+
self._summarize_well(labware_id, well_name)
|
|
192
|
+
for labware_id, well_name in wells.values()
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
def _summarize_well(self, labware_id: str, well_name: str) -> WellInfoSummary:
|
|
196
|
+
well_liquid_info = self.get_well_liquid_info(labware_id, well_name)
|
|
197
|
+
return WellInfoSummary(
|
|
198
|
+
labware_id=labware_id,
|
|
199
|
+
well_name=well_name,
|
|
200
|
+
loaded_volume=_volume_from_info(well_liquid_info.loaded_volume),
|
|
201
|
+
probed_volume=_volume_from_info(well_liquid_info.probed_volume),
|
|
202
|
+
probed_height=_height_from_info(well_liquid_info.probed_height),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@overload
|
|
207
|
+
def _volume_from_info(info: Optional[ProbedVolumeInfo]) -> Optional[float]:
|
|
208
|
+
...
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@overload
|
|
212
|
+
def _volume_from_info(info: Optional[LoadedVolumeInfo]) -> Optional[float]:
|
|
213
|
+
...
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _volume_from_info(
|
|
217
|
+
info: Union[ProbedVolumeInfo, LoadedVolumeInfo, None]
|
|
218
|
+
) -> Optional[float]:
|
|
219
|
+
if info is None:
|
|
220
|
+
return None
|
|
221
|
+
return info.volume
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _height_from_info(info: Optional[ProbedHeightInfo]) -> Optional[float]:
|
|
225
|
+
if info is None:
|
|
226
|
+
return None
|
|
227
|
+
return info.height
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
MaybeClear = TypeVar("MaybeClear")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _none_from_clear(inval: MaybeClear | update_types.ClearType) -> MaybeClear | None:
|
|
234
|
+
if inval == update_types.CLEAR:
|
|
235
|
+
return None
|
|
236
|
+
return inval
|