opentrons 8.3.1a1__py2.py3-none-any.whl → 8.4.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/ot2/mark_bad_calibration.py +2 -0
- opentrons/calibration_storage/ot2/tip_length.py +6 -6
- opentrons/config/advanced_settings.py +9 -11
- opentrons/config/feature_flags.py +0 -4
- opentrons/config/reset.py +7 -2
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/async_serial.py +4 -0
- opentrons/drivers/asyncio/communication/errors.py +41 -8
- opentrons/drivers/asyncio/communication/serial_connection.py +36 -10
- opentrons/drivers/flex_stacker/__init__.py +9 -3
- opentrons/drivers/flex_stacker/abstract.py +140 -15
- opentrons/drivers/flex_stacker/driver.py +593 -47
- opentrons/drivers/flex_stacker/errors.py +64 -0
- opentrons/drivers/flex_stacker/simulator.py +222 -24
- opentrons/drivers/flex_stacker/types.py +211 -15
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/execute.py +4 -2
- opentrons/hardware_control/api.py +5 -0
- opentrons/hardware_control/backends/flex_protocol.py +4 -0
- opentrons/hardware_control/backends/ot3controller.py +12 -1
- opentrons/hardware_control/backends/ot3simulator.py +3 -0
- opentrons/hardware_control/backends/subsystem_manager.py +8 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +10 -6
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +59 -6
- opentrons/hardware_control/modules/__init__.py +12 -1
- opentrons/hardware_control/modules/absorbance_reader.py +11 -9
- opentrons/hardware_control/modules/flex_stacker.py +498 -0
- opentrons/hardware_control/modules/heater_shaker.py +12 -10
- opentrons/hardware_control/modules/magdeck.py +5 -1
- opentrons/hardware_control/modules/tempdeck.py +5 -1
- opentrons/hardware_control/modules/thermocycler.py +15 -14
- opentrons/hardware_control/modules/types.py +191 -1
- opentrons/hardware_control/modules/utils.py +3 -0
- opentrons/hardware_control/motion_utilities.py +20 -0
- opentrons/hardware_control/ot3api.py +145 -15
- opentrons/hardware_control/protocols/liquid_handler.py +47 -1
- opentrons/hardware_control/types.py +6 -0
- opentrons/legacy_commands/commands.py +19 -3
- opentrons/legacy_commands/helpers.py +15 -0
- opentrons/legacy_commands/types.py +3 -2
- opentrons/protocol_api/__init__.py +2 -0
- opentrons/protocol_api/_liquid.py +39 -8
- opentrons/protocol_api/_liquid_properties.py +20 -19
- opentrons/protocol_api/_transfer_liquid_validation.py +91 -0
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/deck_conflict.py +11 -1
- opentrons/protocol_api/core/engine/instrument.py +1233 -65
- opentrons/protocol_api/core/engine/labware.py +8 -4
- opentrons/protocol_api/core/engine/load_labware_params.py +68 -10
- opentrons/protocol_api/core/engine/module_core.py +118 -2
- opentrons/protocol_api/core/engine/protocol.py +253 -11
- opentrons/protocol_api/core/engine/stringify.py +19 -8
- opentrons/protocol_api/core/engine/transfer_components_executor.py +853 -0
- opentrons/protocol_api/core/engine/well.py +60 -5
- opentrons/protocol_api/core/instrument.py +65 -19
- opentrons/protocol_api/core/labware.py +6 -2
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +7 -3
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +69 -21
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +8 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +36 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +25 -1
- opentrons/protocol_api/core/legacy/load_info.py +4 -12
- opentrons/protocol_api/core/legacy/module_geometry.py +6 -1
- opentrons/protocol_api/core/legacy/well_geometry.py +3 -3
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +67 -21
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +21 -1
- opentrons/protocol_api/instrument_context.py +246 -123
- opentrons/protocol_api/labware.py +75 -11
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +156 -16
- opentrons/protocol_api/validation.py +51 -41
- opentrons/protocol_engine/__init__.py +21 -2
- opentrons/protocol_engine/actions/actions.py +5 -5
- opentrons/protocol_engine/clients/sync_client.py +6 -0
- opentrons/protocol_engine/commands/__init__.py +30 -0
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +0 -1
- opentrons/protocol_engine/commands/air_gap_in_place.py +3 -2
- opentrons/protocol_engine/commands/aspirate.py +6 -2
- opentrons/protocol_engine/commands/aspirate_in_place.py +3 -1
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +237 -0
- opentrons/protocol_engine/commands/blow_out.py +2 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +4 -1
- opentrons/protocol_engine/commands/command_unions.py +69 -0
- opentrons/protocol_engine/commands/configure_for_volume.py +3 -0
- opentrons/protocol_engine/commands/dispense.py +3 -1
- opentrons/protocol_engine/commands/dispense_in_place.py +3 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +240 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- opentrons/protocol_engine/commands/evotip_dispense.py +6 -7
- opentrons/protocol_engine/commands/evotip_seal_pipette.py +24 -29
- opentrons/protocol_engine/commands/evotip_unseal_pipette.py +1 -7
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +106 -0
- opentrons/protocol_engine/commands/flex_stacker/close_latch.py +72 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +15 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +161 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +164 -0
- opentrons/protocol_engine/commands/flex_stacker/open_latch.py +70 -0
- opentrons/protocol_engine/commands/flex_stacker/prepare_shuttle.py +112 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +394 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +190 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +288 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +24 -0
- opentrons/protocol_engine/commands/liquid_probe.py +21 -12
- opentrons/protocol_engine/commands/load_labware.py +42 -39
- opentrons/protocol_engine/commands/load_lid.py +21 -13
- opentrons/protocol_engine/commands/load_lid_stack.py +130 -47
- opentrons/protocol_engine/commands/load_module.py +18 -17
- opentrons/protocol_engine/commands/load_pipette.py +3 -0
- opentrons/protocol_engine/commands/move_labware.py +139 -20
- opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +154 -7
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +17 -2
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +3 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +6 -1
- opentrons/protocol_engine/errors/__init__.py +8 -0
- opentrons/protocol_engine/errors/exceptions.py +50 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +233 -26
- opentrons/protocol_engine/execution/tip_handler.py +14 -5
- opentrons/protocol_engine/labware_offset_standardization.py +173 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +94 -2
- opentrons/protocol_engine/resources/deck_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_data_provider.py +32 -12
- opentrons/protocol_engine/resources/labware_validation.py +7 -5
- opentrons/protocol_engine/slot_standardization.py +11 -23
- opentrons/protocol_engine/state/addressable_areas.py +84 -46
- opentrons/protocol_engine/state/frustum_helpers.py +26 -10
- opentrons/protocol_engine/state/geometry.py +683 -100
- opentrons/protocol_engine/state/labware.py +252 -55
- opentrons/protocol_engine/state/module_substates/__init__.py +4 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +68 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +22 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +13 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +20 -0
- opentrons/protocol_engine/state/modules.py +178 -52
- opentrons/protocol_engine/state/pipettes.py +54 -0
- opentrons/protocol_engine/state/state.py +1 -1
- opentrons/protocol_engine/state/tips.py +14 -0
- opentrons/protocol_engine/state/update_types.py +180 -25
- opentrons/protocol_engine/state/wells.py +54 -8
- opentrons/protocol_engine/types/__init__.py +292 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +72 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +110 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +108 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +33 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +137 -0
- opentrons/protocol_engine/types/location.py +193 -0
- opentrons/protocol_engine/types/module.py +269 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +107 -0
- opentrons/protocol_reader/extract_labware_definitions.py +7 -3
- opentrons/protocol_reader/file_format_validator.py +5 -3
- opentrons/protocol_runner/json_translator.py +4 -2
- opentrons/protocol_runner/legacy_command_mapper.py +6 -2
- opentrons/protocol_runner/run_orchestrator.py +4 -1
- opentrons/protocols/advanced_control/transfers/common.py +48 -1
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +204 -0
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +16 -3
- opentrons/protocols/labware.py +5 -6
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +15 -6
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/METADATA +4 -4
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/RECORD +188 -148
- opentrons/calibration_storage/ot2/models/defaults.py +0 -0
- opentrons/calibration_storage/ot3/models/defaults.py +0 -0
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_engine/types.py +0 -1311
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/LICENSE +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/WHEEL +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.1a1.dist-info → opentrons-8.4.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,29 +1,32 @@
|
|
|
1
1
|
"""ProtocolEngine class definition."""
|
|
2
|
+
|
|
2
3
|
from contextlib import AsyncExitStack
|
|
3
4
|
from logging import getLogger
|
|
4
5
|
from typing import Dict, Optional, Union, AsyncGenerator, Callable
|
|
5
|
-
from opentrons.protocol_engine.actions.actions import (
|
|
6
|
-
ResumeFromRecoveryAction,
|
|
7
|
-
SetErrorRecoveryPolicyAction,
|
|
8
|
-
)
|
|
9
6
|
|
|
10
|
-
from opentrons.protocols.models import LabwareDefinition
|
|
11
|
-
from opentrons.hardware_control import HardwareControlAPI
|
|
12
|
-
from opentrons.hardware_control.modules import AbstractModule as HardwareModuleAPI
|
|
13
|
-
from opentrons.hardware_control.types import PauseType as HardwarePauseType
|
|
14
7
|
from opentrons_shared_data.errors import (
|
|
15
8
|
ErrorCodes,
|
|
16
9
|
EnumeratedError,
|
|
17
10
|
)
|
|
11
|
+
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
18
12
|
|
|
13
|
+
from opentrons.hardware_control import HardwareControlAPI
|
|
14
|
+
from opentrons.hardware_control.modules import AbstractModule as HardwareModuleAPI
|
|
15
|
+
from opentrons.hardware_control.types import PauseType as HardwarePauseType
|
|
16
|
+
|
|
17
|
+
from .actions.actions import (
|
|
18
|
+
ResumeFromRecoveryAction,
|
|
19
|
+
SetErrorRecoveryPolicyAction,
|
|
20
|
+
)
|
|
19
21
|
from .errors import ProtocolCommandFailedError, ErrorOccurrence, CommandNotAllowedError
|
|
20
22
|
from .errors.exceptions import EStopActivatedError
|
|
21
23
|
from .error_recovery_policy import ErrorRecoveryPolicy
|
|
22
|
-
from . import commands, slot_standardization
|
|
24
|
+
from . import commands, slot_standardization, labware_offset_standardization
|
|
23
25
|
from .resources import ModelUtils, ModuleDataProvider, FileProvider
|
|
24
26
|
from .types import (
|
|
25
27
|
LabwareOffset,
|
|
26
28
|
LabwareOffsetCreate,
|
|
29
|
+
LegacyLabwareOffsetCreate,
|
|
27
30
|
LabwareUri,
|
|
28
31
|
ModuleModel,
|
|
29
32
|
Liquid,
|
|
@@ -516,15 +519,21 @@ class ProtocolEngine:
|
|
|
516
519
|
)
|
|
517
520
|
)
|
|
518
521
|
|
|
519
|
-
def add_labware_offset(
|
|
522
|
+
def add_labware_offset(
|
|
523
|
+
self, request: LabwareOffsetCreate | LegacyLabwareOffsetCreate
|
|
524
|
+
) -> LabwareOffset:
|
|
520
525
|
"""Add a new labware offset and return it.
|
|
521
526
|
|
|
522
527
|
The added offset will apply to subsequent `LoadLabwareCommand`s.
|
|
523
528
|
|
|
524
529
|
To retrieve offsets later, see `.state_view.labware`.
|
|
525
530
|
"""
|
|
526
|
-
|
|
527
|
-
|
|
531
|
+
internal_request = (
|
|
532
|
+
labware_offset_standardization.standardize_labware_offset_create(
|
|
533
|
+
request,
|
|
534
|
+
self.state_view.config.robot_type,
|
|
535
|
+
self.state_view.addressable_areas.deck_definition,
|
|
536
|
+
)
|
|
528
537
|
)
|
|
529
538
|
|
|
530
539
|
labware_offset_id = self._model_utils.generate_id()
|
|
@@ -533,7 +542,7 @@ class ProtocolEngine:
|
|
|
533
542
|
AddLabwareOffsetAction(
|
|
534
543
|
labware_offset_id=labware_offset_id,
|
|
535
544
|
created_at=created_at,
|
|
536
|
-
request=
|
|
545
|
+
request=internal_request,
|
|
537
546
|
)
|
|
538
547
|
)
|
|
539
548
|
return self.state_view.labware.get_labware_offset(
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"""Deck configuration resource provider."""
|
|
2
|
+
|
|
2
3
|
from typing import List, Set, Tuple
|
|
3
4
|
|
|
4
|
-
from opentrons_shared_data.deck.types import
|
|
5
|
+
from opentrons_shared_data.deck.types import (
|
|
6
|
+
DeckDefinitionV5,
|
|
7
|
+
CutoutFixture,
|
|
8
|
+
)
|
|
5
9
|
|
|
6
10
|
from opentrons.types import DeckSlotName
|
|
7
11
|
|
|
@@ -17,6 +21,7 @@ from ..errors import (
|
|
|
17
21
|
CutoutDoesNotExistError,
|
|
18
22
|
FixtureDoesNotExistError,
|
|
19
23
|
AddressableAreaDoesNotExistError,
|
|
24
|
+
SlotDoesNotExistError,
|
|
20
25
|
)
|
|
21
26
|
|
|
22
27
|
|
|
@@ -98,12 +103,15 @@ def get_potential_cutout_fixtures(
|
|
|
98
103
|
def get_addressable_area_from_name(
|
|
99
104
|
addressable_area_name: str,
|
|
100
105
|
cutout_position: DeckPoint,
|
|
101
|
-
base_slot: DeckSlotName,
|
|
102
106
|
deck_definition: DeckDefinitionV5,
|
|
103
107
|
) -> AddressableArea:
|
|
104
108
|
"""Given a name and a cutout position, get an addressable area on the deck."""
|
|
105
109
|
for addressable_area in deck_definition["locations"]["addressableAreas"]:
|
|
106
110
|
if addressable_area["id"] == addressable_area_name:
|
|
111
|
+
cutout_id, _ = get_potential_cutout_fixtures(
|
|
112
|
+
addressable_area_name, deck_definition
|
|
113
|
+
)
|
|
114
|
+
base_slot = get_deck_slot_for_cutout_id(cutout_id)
|
|
107
115
|
area_offset = addressable_area["offsetFromCutoutFixture"]
|
|
108
116
|
position = AddressableOffsetVector(
|
|
109
117
|
x=area_offset[0] + cutout_position.x,
|
|
@@ -130,3 +138,87 @@ def get_addressable_area_from_name(
|
|
|
130
138
|
raise AddressableAreaDoesNotExistError(
|
|
131
139
|
f"Could not find addressable area with name {addressable_area_name}"
|
|
132
140
|
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_deck_slot_for_cutout_id(cutout_id: str) -> DeckSlotName:
|
|
144
|
+
"""Get the corresponding deck slot for an addressable area."""
|
|
145
|
+
try:
|
|
146
|
+
return CUTOUT_TO_DECK_SLOT_MAP[cutout_id]
|
|
147
|
+
except KeyError:
|
|
148
|
+
raise CutoutDoesNotExistError(f"Could not find data for cutout {cutout_id}")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_cutout_id_by_deck_slot_name(slot_name: DeckSlotName) -> str:
|
|
152
|
+
"""Get the Cutout ID of a given Deck Slot by Deck Slot Name."""
|
|
153
|
+
try:
|
|
154
|
+
return DECK_SLOT_TO_CUTOUT_MAP[slot_name]
|
|
155
|
+
except KeyError:
|
|
156
|
+
raise SlotDoesNotExistError(f"Could not find data for slot {slot_name.value}")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_labware_hosting_addressable_area_name_for_cutout_and_cutout_fixture(
|
|
160
|
+
cutout_id: str, cutout_fixture_id: str, deck_definition: DeckDefinitionV5
|
|
161
|
+
) -> str:
|
|
162
|
+
"""Get the first addressable area that can contain labware for a cutout and fixture.
|
|
163
|
+
|
|
164
|
+
This probably isn't relevant outside of labware offset locations, where (for now) nothing
|
|
165
|
+
provides more than one labware-containing addressable area.
|
|
166
|
+
"""
|
|
167
|
+
for cutoutFixture in deck_definition["cutoutFixtures"]:
|
|
168
|
+
if cutoutFixture["id"] != cutout_fixture_id:
|
|
169
|
+
continue
|
|
170
|
+
provided_aas = cutoutFixture["providesAddressableAreas"].get(cutout_id, None)
|
|
171
|
+
if provided_aas is None:
|
|
172
|
+
raise CutoutDoesNotExistError(
|
|
173
|
+
f"{cutout_fixture_id} does not go in {cutout_id}"
|
|
174
|
+
)
|
|
175
|
+
for aa_id in provided_aas:
|
|
176
|
+
for addressable_area in deck_definition["locations"]["addressableAreas"]:
|
|
177
|
+
if addressable_area["id"] != aa_id:
|
|
178
|
+
continue
|
|
179
|
+
# TODO: In deck def v6 this will be easier, but as of right now there isn't really
|
|
180
|
+
# a way to tell from an addressable area whether it takes labware so let's take the
|
|
181
|
+
# first one
|
|
182
|
+
return aa_id
|
|
183
|
+
raise AddressableAreaDoesNotExistError(
|
|
184
|
+
f"Could not find an addressable area that allows labware from cutout fixture {cutout_fixture_id} in cutout {cutout_id}"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
raise FixtureDoesNotExistError(f"Could not find entry for {cutout_fixture_id}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# This is a temporary shim while Protocol Engine's conflict-checking code
|
|
191
|
+
# can only take deck slots as input.
|
|
192
|
+
# Long-term solution: Check for conflicts based on bounding boxes, not slot adjacencies.
|
|
193
|
+
# Shorter-term: Change the conflict-checking code to take cutouts instead of deck slots.
|
|
194
|
+
CUTOUT_TO_DECK_SLOT_MAP: dict[str, DeckSlotName] = {
|
|
195
|
+
# OT-2
|
|
196
|
+
"cutout1": DeckSlotName.SLOT_1,
|
|
197
|
+
"cutout2": DeckSlotName.SLOT_2,
|
|
198
|
+
"cutout3": DeckSlotName.SLOT_3,
|
|
199
|
+
"cutout4": DeckSlotName.SLOT_4,
|
|
200
|
+
"cutout5": DeckSlotName.SLOT_5,
|
|
201
|
+
"cutout6": DeckSlotName.SLOT_6,
|
|
202
|
+
"cutout7": DeckSlotName.SLOT_7,
|
|
203
|
+
"cutout8": DeckSlotName.SLOT_8,
|
|
204
|
+
"cutout9": DeckSlotName.SLOT_9,
|
|
205
|
+
"cutout10": DeckSlotName.SLOT_10,
|
|
206
|
+
"cutout11": DeckSlotName.SLOT_11,
|
|
207
|
+
"cutout12": DeckSlotName.FIXED_TRASH,
|
|
208
|
+
# Flex
|
|
209
|
+
"cutoutA1": DeckSlotName.SLOT_A1,
|
|
210
|
+
"cutoutA2": DeckSlotName.SLOT_A2,
|
|
211
|
+
"cutoutA3": DeckSlotName.SLOT_A3,
|
|
212
|
+
"cutoutB1": DeckSlotName.SLOT_B1,
|
|
213
|
+
"cutoutB2": DeckSlotName.SLOT_B2,
|
|
214
|
+
"cutoutB3": DeckSlotName.SLOT_B3,
|
|
215
|
+
"cutoutC1": DeckSlotName.SLOT_C1,
|
|
216
|
+
"cutoutC2": DeckSlotName.SLOT_C2,
|
|
217
|
+
"cutoutC3": DeckSlotName.SLOT_C3,
|
|
218
|
+
"cutoutD1": DeckSlotName.SLOT_D1,
|
|
219
|
+
"cutoutD2": DeckSlotName.SLOT_D2,
|
|
220
|
+
"cutoutD3": DeckSlotName.SLOT_D3,
|
|
221
|
+
}
|
|
222
|
+
DECK_SLOT_TO_CUTOUT_MAP = {
|
|
223
|
+
deck_slot: cutout for cutout, deck_slot in CUTOUT_TO_DECK_SLOT_MAP.items()
|
|
224
|
+
}
|
|
@@ -10,7 +10,7 @@ from opentrons_shared_data.deck import (
|
|
|
10
10
|
DEFAULT_DECK_DEFINITION_VERSION,
|
|
11
11
|
)
|
|
12
12
|
from opentrons_shared_data.deck.types import DeckDefinitionV5
|
|
13
|
-
from
|
|
13
|
+
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
14
14
|
from opentrons.types import DeckSlotName
|
|
15
15
|
|
|
16
16
|
from ..types import (
|
|
@@ -6,7 +6,12 @@ abstract away rough edges until we can improve those underlying interfaces.
|
|
|
6
6
|
import logging
|
|
7
7
|
from anyio import to_thread
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from opentrons_shared_data.labware.labware_definition import (
|
|
10
|
+
LabwareDefinition,
|
|
11
|
+
LabwareDefinition3,
|
|
12
|
+
labware_definition_type_adapter,
|
|
13
|
+
)
|
|
14
|
+
|
|
10
15
|
from opentrons.protocols.labware import get_labware_definition
|
|
11
16
|
|
|
12
17
|
# TODO (lc 09-26-2022) We should conditionally import ot2 or ot3 calibration
|
|
@@ -44,7 +49,7 @@ class LabwareDataProvider:
|
|
|
44
49
|
def _get_labware_definition_sync(
|
|
45
50
|
load_name: str, namespace: str, version: int
|
|
46
51
|
) -> LabwareDefinition:
|
|
47
|
-
return
|
|
52
|
+
return labware_definition_type_adapter.validate_python(
|
|
48
53
|
get_labware_definition(load_name, namespace, version)
|
|
49
54
|
)
|
|
50
55
|
|
|
@@ -72,15 +77,30 @@ class LabwareDataProvider:
|
|
|
72
77
|
labware_definition: LabwareDefinition,
|
|
73
78
|
nominal_fallback: float,
|
|
74
79
|
) -> float:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
if isinstance(labware_definition, LabwareDefinition3):
|
|
81
|
+
# FIXME(mm, 2025-02-19): This needs to be resolved for v8.4.0.
|
|
82
|
+
# Tip length calibration internals don't yet support schema 3 because
|
|
83
|
+
# it's probably an incompatible change at the filesystem level
|
|
84
|
+
# (not downgrade-safe), and because robot-server's calibration sessions
|
|
85
|
+
# are built atop opentrons.protocol_api.core.legacy, which does not (yet?)
|
|
86
|
+
# support labware schema 3.
|
|
87
|
+
# https://opentrons.atlassian.net/browse/EXEC-1230
|
|
88
|
+
log.warning(
|
|
89
|
+
f"Tip rack"
|
|
90
|
+
f" {labware_definition.namespace}/{labware_definition.parameters.loadName}/{labware_definition.version}"
|
|
91
|
+
f" has schema 3, so tip length calibration is currently unsupported."
|
|
92
|
+
f" Using nominal fallback of {nominal_fallback}."
|
|
84
93
|
)
|
|
85
|
-
log.debug(message, exc_info=e)
|
|
86
94
|
return nominal_fallback
|
|
95
|
+
else:
|
|
96
|
+
try:
|
|
97
|
+
return instr_cal.load_tip_length_for_pipette(
|
|
98
|
+
pipette_serial, labware_definition
|
|
99
|
+
).tip_length
|
|
100
|
+
except TipLengthCalNotFound as e:
|
|
101
|
+
message = (
|
|
102
|
+
f"No calibrated tip length found for {pipette_serial},"
|
|
103
|
+
f" using nominal fallback value of {nominal_fallback}"
|
|
104
|
+
)
|
|
105
|
+
log.debug(message, exc_info=e)
|
|
106
|
+
return nominal_fallback
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""Validation file for labware role and location checking functions."""
|
|
2
2
|
|
|
3
|
-
from opentrons_shared_data.labware.labware_definition import
|
|
4
|
-
|
|
3
|
+
from opentrons_shared_data.labware.labware_definition import (
|
|
4
|
+
LabwareDefinition,
|
|
5
|
+
LabwareRole,
|
|
6
|
+
)
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def is_flex_trash(load_name: str) -> bool:
|
|
@@ -14,9 +16,9 @@ def is_absorbance_reader_lid(load_name: str) -> bool:
|
|
|
14
16
|
return load_name == "opentrons_flex_lid_absorbance_plate_reader_module"
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
def
|
|
18
|
-
"""Check if a labware is
|
|
19
|
-
return load_name == "
|
|
19
|
+
def is_lid_stack(load_name: str) -> bool:
|
|
20
|
+
"""Check if a labware object is a system lid stack object."""
|
|
21
|
+
return load_name == "protocol_engine_lid_stack_object"
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
def validate_definition_is_labware(definition: LabwareDefinition) -> bool:
|
|
@@ -14,7 +14,6 @@ This module does that conversion, for any Protocol Engine input that contains a
|
|
|
14
14
|
deck slot.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
|
|
18
17
|
from typing import Any, Callable, Dict, Type
|
|
19
18
|
|
|
20
19
|
from opentrons_shared_data.robot.types import RobotType
|
|
@@ -22,32 +21,15 @@ from opentrons_shared_data.robot.types import RobotType
|
|
|
22
21
|
from . import commands
|
|
23
22
|
from .types import (
|
|
24
23
|
OFF_DECK_LOCATION,
|
|
24
|
+
SYSTEM_LOCATION,
|
|
25
25
|
DeckSlotLocation,
|
|
26
|
-
|
|
26
|
+
LoadableLabwareLocation,
|
|
27
27
|
AddressableAreaLocation,
|
|
28
|
-
LabwareOffsetCreate,
|
|
29
28
|
ModuleLocation,
|
|
30
29
|
OnLabwareLocation,
|
|
31
30
|
)
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
def standardize_labware_offset(
|
|
35
|
-
original: LabwareOffsetCreate, robot_type: RobotType
|
|
36
|
-
) -> LabwareOffsetCreate:
|
|
37
|
-
"""Convert the deck slot in the given `LabwareOffsetCreate` to match the given robot type."""
|
|
38
|
-
return original.model_copy(
|
|
39
|
-
update={
|
|
40
|
-
"location": original.location.model_copy(
|
|
41
|
-
update={
|
|
42
|
-
"slotName": original.location.slotName.to_equivalent_for_robot_type(
|
|
43
|
-
robot_type
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
33
|
def standardize_command(
|
|
52
34
|
original: commands.CommandCreate, robot_type: RobotType
|
|
53
35
|
) -> commands.CommandCreate:
|
|
@@ -119,15 +101,21 @@ _standardize_command_functions: Dict[
|
|
|
119
101
|
|
|
120
102
|
|
|
121
103
|
def _standardize_labware_location(
|
|
122
|
-
original:
|
|
123
|
-
) ->
|
|
104
|
+
original: LoadableLabwareLocation, robot_type: RobotType
|
|
105
|
+
) -> LoadableLabwareLocation:
|
|
124
106
|
if isinstance(original, DeckSlotLocation):
|
|
125
107
|
return _standardize_deck_slot_location(original, robot_type)
|
|
126
108
|
elif (
|
|
127
109
|
isinstance(
|
|
128
|
-
original,
|
|
110
|
+
original,
|
|
111
|
+
(
|
|
112
|
+
ModuleLocation,
|
|
113
|
+
OnLabwareLocation,
|
|
114
|
+
AddressableAreaLocation,
|
|
115
|
+
),
|
|
129
116
|
)
|
|
130
117
|
or original == OFF_DECK_LOCATION
|
|
118
|
+
or original == SYSTEM_LOCATION
|
|
131
119
|
):
|
|
132
120
|
return original
|
|
133
121
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Basic addressable area data state and store."""
|
|
2
|
+
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from functools import cached_property
|
|
4
5
|
from typing import Dict, List, Optional, Set
|
|
@@ -112,43 +113,6 @@ def _get_conflicting_addressable_areas_error_string(
|
|
|
112
113
|
return ", ".join(display_names)
|
|
113
114
|
|
|
114
115
|
|
|
115
|
-
# This is a temporary shim while Protocol Engine's conflict-checking code
|
|
116
|
-
# can only take deck slots as input.
|
|
117
|
-
# Long-term solution: Check for conflicts based on bounding boxes, not slot adjacencies.
|
|
118
|
-
# Shorter-term: Change the conflict-checking code to take cutouts instead of deck slots.
|
|
119
|
-
CUTOUT_TO_DECK_SLOT_MAP: Dict[str, DeckSlotName] = {
|
|
120
|
-
# OT-2
|
|
121
|
-
"cutout1": DeckSlotName.SLOT_1,
|
|
122
|
-
"cutout2": DeckSlotName.SLOT_2,
|
|
123
|
-
"cutout3": DeckSlotName.SLOT_3,
|
|
124
|
-
"cutout4": DeckSlotName.SLOT_4,
|
|
125
|
-
"cutout5": DeckSlotName.SLOT_5,
|
|
126
|
-
"cutout6": DeckSlotName.SLOT_6,
|
|
127
|
-
"cutout7": DeckSlotName.SLOT_7,
|
|
128
|
-
"cutout8": DeckSlotName.SLOT_8,
|
|
129
|
-
"cutout9": DeckSlotName.SLOT_9,
|
|
130
|
-
"cutout10": DeckSlotName.SLOT_10,
|
|
131
|
-
"cutout11": DeckSlotName.SLOT_11,
|
|
132
|
-
"cutout12": DeckSlotName.FIXED_TRASH,
|
|
133
|
-
# Flex
|
|
134
|
-
"cutoutA1": DeckSlotName.SLOT_A1,
|
|
135
|
-
"cutoutA2": DeckSlotName.SLOT_A2,
|
|
136
|
-
"cutoutA3": DeckSlotName.SLOT_A3,
|
|
137
|
-
"cutoutB1": DeckSlotName.SLOT_B1,
|
|
138
|
-
"cutoutB2": DeckSlotName.SLOT_B2,
|
|
139
|
-
"cutoutB3": DeckSlotName.SLOT_B3,
|
|
140
|
-
"cutoutC1": DeckSlotName.SLOT_C1,
|
|
141
|
-
"cutoutC2": DeckSlotName.SLOT_C2,
|
|
142
|
-
"cutoutC3": DeckSlotName.SLOT_C3,
|
|
143
|
-
"cutoutD1": DeckSlotName.SLOT_D1,
|
|
144
|
-
"cutoutD2": DeckSlotName.SLOT_D2,
|
|
145
|
-
"cutoutD3": DeckSlotName.SLOT_D3,
|
|
146
|
-
}
|
|
147
|
-
DECK_SLOT_TO_CUTOUT_MAP = {
|
|
148
|
-
deck_slot: cutout for cutout, deck_slot in CUTOUT_TO_DECK_SLOT_MAP.items()
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
116
|
class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
153
117
|
"""Addressable area state container."""
|
|
154
118
|
|
|
@@ -221,13 +185,11 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
221
185
|
cutout_position = deck_configuration_provider.get_cutout_position(
|
|
222
186
|
cutout_id, deck_definition
|
|
223
187
|
)
|
|
224
|
-
base_slot = CUTOUT_TO_DECK_SLOT_MAP[cutout_id]
|
|
225
188
|
for addressable_area_name in provided_addressable_areas:
|
|
226
189
|
addressable_areas.append(
|
|
227
190
|
deck_configuration_provider.get_addressable_area_from_name(
|
|
228
191
|
addressable_area_name=addressable_area_name,
|
|
229
192
|
cutout_position=cutout_position,
|
|
230
|
-
base_slot=base_slot,
|
|
231
193
|
deck_definition=deck_definition,
|
|
232
194
|
)
|
|
233
195
|
)
|
|
@@ -242,12 +204,10 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
242
204
|
cutout_position = deck_configuration_provider.get_cutout_position(
|
|
243
205
|
cutout_id, self._state.deck_definition
|
|
244
206
|
)
|
|
245
|
-
base_slot = CUTOUT_TO_DECK_SLOT_MAP[cutout_id]
|
|
246
207
|
addressable_area = (
|
|
247
208
|
deck_configuration_provider.get_addressable_area_from_name(
|
|
248
209
|
addressable_area_name=addressable_area_name,
|
|
249
210
|
cutout_position=cutout_position,
|
|
250
|
-
base_slot=base_slot,
|
|
251
211
|
deck_definition=self._state.deck_definition,
|
|
252
212
|
)
|
|
253
213
|
)
|
|
@@ -300,6 +260,11 @@ class AddressableAreaView:
|
|
|
300
260
|
"""
|
|
301
261
|
self._state = state
|
|
302
262
|
|
|
263
|
+
@cached_property
|
|
264
|
+
def deck_definition(self) -> DeckDefinitionV5:
|
|
265
|
+
"""The full deck definition."""
|
|
266
|
+
return self._state.deck_definition
|
|
267
|
+
|
|
303
268
|
@cached_property
|
|
304
269
|
def deck_extents(self) -> Point:
|
|
305
270
|
"""The maximum space on the deck."""
|
|
@@ -426,11 +391,9 @@ class AddressableAreaView:
|
|
|
426
391
|
cutout_position = deck_configuration_provider.get_cutout_position(
|
|
427
392
|
cutout_id, self._state.deck_definition
|
|
428
393
|
)
|
|
429
|
-
base_slot = CUTOUT_TO_DECK_SLOT_MAP[cutout_id]
|
|
430
394
|
return deck_configuration_provider.get_addressable_area_from_name(
|
|
431
395
|
addressable_area_name=addressable_area_name,
|
|
432
396
|
cutout_position=cutout_position,
|
|
433
|
-
base_slot=base_slot,
|
|
434
397
|
deck_definition=self._state.deck_definition,
|
|
435
398
|
)
|
|
436
399
|
|
|
@@ -526,7 +489,7 @@ class AddressableAreaView:
|
|
|
526
489
|
|
|
527
490
|
def get_cutout_id_by_deck_slot_name(self, slot_name: DeckSlotName) -> str:
|
|
528
491
|
"""Get the Cutout ID of a given Deck Slot by Deck Slot Name."""
|
|
529
|
-
return
|
|
492
|
+
return deck_configuration_provider.get_cutout_id_by_deck_slot_name(slot_name)
|
|
530
493
|
|
|
531
494
|
def get_fixture_by_deck_slot_name(
|
|
532
495
|
self, slot_name: DeckSlotName
|
|
@@ -534,7 +497,9 @@ class AddressableAreaView:
|
|
|
534
497
|
"""Get the Cutout Fixture currently loaded where a specific Deck Slot would be."""
|
|
535
498
|
deck_config = self._state.deck_configuration
|
|
536
499
|
if deck_config:
|
|
537
|
-
slot_cutout_id =
|
|
500
|
+
slot_cutout_id = (
|
|
501
|
+
deck_configuration_provider.get_cutout_id_by_deck_slot_name(slot_name)
|
|
502
|
+
)
|
|
538
503
|
slot_cutout_fixture = None
|
|
539
504
|
# This will only ever be one under current assumptions
|
|
540
505
|
for (
|
|
@@ -571,7 +536,9 @@ class AddressableAreaView:
|
|
|
571
536
|
"""Get the serial number provided by the deck configuration for a Fixture at a given location."""
|
|
572
537
|
deck_config = self._state.deck_configuration
|
|
573
538
|
if deck_config:
|
|
574
|
-
slot_cutout_id =
|
|
539
|
+
slot_cutout_id = (
|
|
540
|
+
deck_configuration_provider.get_cutout_id_by_deck_slot_name(slot_name)
|
|
541
|
+
)
|
|
575
542
|
# This will only ever be one under current assumptions
|
|
576
543
|
for (
|
|
577
544
|
cutout_id,
|
|
@@ -582,6 +549,44 @@ class AddressableAreaView:
|
|
|
582
549
|
return opentrons_module_serial_number
|
|
583
550
|
return None
|
|
584
551
|
|
|
552
|
+
def get_serial_number_by_cutout_id(self, slot_cutout_id: str) -> str | None:
|
|
553
|
+
"""Gets serial number from deck at a given cutout ID if one exists."""
|
|
554
|
+
deck_config = self._state.deck_configuration
|
|
555
|
+
if deck_config:
|
|
556
|
+
for (
|
|
557
|
+
cutout_id,
|
|
558
|
+
cutout_fixture_id,
|
|
559
|
+
opentrons_module_serial_number,
|
|
560
|
+
) in deck_config:
|
|
561
|
+
if cutout_id == slot_cutout_id:
|
|
562
|
+
return opentrons_module_serial_number
|
|
563
|
+
return None
|
|
564
|
+
|
|
565
|
+
def get_fixture_serial_from_deck_configuration_by_addressable_area(
|
|
566
|
+
self, addressable_area_name: str
|
|
567
|
+
) -> Optional[str]:
|
|
568
|
+
"""Get the serial number provided by the deck configuration for a Fixture that provides a given addressable area."""
|
|
569
|
+
deck_config = self._state.deck_configuration
|
|
570
|
+
if deck_config:
|
|
571
|
+
potential_fixtures = (
|
|
572
|
+
deck_configuration_provider.get_potential_cutout_fixtures(
|
|
573
|
+
addressable_area_name, self._state.deck_definition
|
|
574
|
+
)
|
|
575
|
+
)
|
|
576
|
+
slot_cutout_id = potential_fixtures[0]
|
|
577
|
+
fixture_ids = [
|
|
578
|
+
fixture.cutout_fixture_id for fixture in potential_fixtures[1]
|
|
579
|
+
]
|
|
580
|
+
# This will only ever be one under current assumptions
|
|
581
|
+
for (
|
|
582
|
+
cutout_id,
|
|
583
|
+
cutout_fixture_id,
|
|
584
|
+
opentrons_module_serial_number,
|
|
585
|
+
) in deck_config:
|
|
586
|
+
if cutout_id == slot_cutout_id and cutout_fixture_id in fixture_ids:
|
|
587
|
+
return opentrons_module_serial_number
|
|
588
|
+
return None
|
|
589
|
+
|
|
585
590
|
def get_slot_definition(self, slot_id: str) -> SlotDefV3:
|
|
586
591
|
"""Get the definition of a slot in the deck.
|
|
587
592
|
|
|
@@ -658,3 +663,36 @@ class AddressableAreaView:
|
|
|
658
663
|
raise AreaNotInDeckConfigurationError(
|
|
659
664
|
f"{addressable_area_name} not provided by deck configuration."
|
|
660
665
|
)
|
|
666
|
+
|
|
667
|
+
def get_current_potential_cutout_fixtures_for_addressable_area(
|
|
668
|
+
self, addressable_area_name: str
|
|
669
|
+
) -> tuple[str, Set[PotentialCutoutFixture]]:
|
|
670
|
+
"""Get the set of cutout fixtures that might provide a given addressable area.
|
|
671
|
+
|
|
672
|
+
This takes into account the constraints already established by load commands or by a loaded deck
|
|
673
|
+
configuration, and may therefore return different results for the same addressable area at
|
|
674
|
+
different points in the protocol after deck configuration constraints have changed.
|
|
675
|
+
|
|
676
|
+
This returns the common cutout id and the potential fixtures.
|
|
677
|
+
"""
|
|
678
|
+
(
|
|
679
|
+
cutout_id,
|
|
680
|
+
base_potential_fixtures,
|
|
681
|
+
) = deck_configuration_provider.get_potential_cutout_fixtures(
|
|
682
|
+
addressable_area_name, self._state.deck_definition
|
|
683
|
+
)
|
|
684
|
+
try:
|
|
685
|
+
loaded_potential_fixtures = (
|
|
686
|
+
self._state.potential_cutout_fixtures_by_cutout_id[cutout_id]
|
|
687
|
+
)
|
|
688
|
+
return cutout_id, loaded_potential_fixtures.intersection(
|
|
689
|
+
base_potential_fixtures
|
|
690
|
+
)
|
|
691
|
+
except KeyError:
|
|
692
|
+
# If there was a key error here, it's because this function was (eventually) called
|
|
693
|
+
# from the body of a command implementation whose state update will load the
|
|
694
|
+
# addressable area it's querying... but that state update has not been submitted
|
|
695
|
+
# and processed, so nothing has created the entry for this cutout id yet. Do what
|
|
696
|
+
# we'll do when we actually get to that state update, which is apply the base
|
|
697
|
+
# potential fixtures from the deck def.
|
|
698
|
+
return cutout_id, base_potential_fixtures
|
|
@@ -5,6 +5,10 @@ from math import isclose
|
|
|
5
5
|
|
|
6
6
|
from ..errors.exceptions import InvalidLiquidHeightFound
|
|
7
7
|
|
|
8
|
+
from opentrons.protocol_engine.types.liquid_level_detection import (
|
|
9
|
+
LiquidTrackingType,
|
|
10
|
+
SimulatedProbeResult,
|
|
11
|
+
)
|
|
8
12
|
from opentrons_shared_data.labware.labware_definition import (
|
|
9
13
|
InnerWellGeometry,
|
|
10
14
|
WellSegment,
|
|
@@ -241,9 +245,8 @@ def _get_segment_capacity(segment: WellSegment) -> float:
|
|
|
241
245
|
def get_well_volumetric_capacity(
|
|
242
246
|
well_geometry: InnerWellGeometry,
|
|
243
247
|
) -> List[Tuple[float, float]]:
|
|
244
|
-
"""Return the
|
|
245
|
-
#
|
|
246
|
-
# {top_height_0: volume_0, top_height_1: volume_1, top_height_2: volume_2}
|
|
248
|
+
"""Return the volumetric capacity of a well as a list of pairs relating segment heights to volumes."""
|
|
249
|
+
# [(top_height_0, section_0_volume), (top_height_1, section_1_volume), ...]
|
|
247
250
|
well_volume = []
|
|
248
251
|
|
|
249
252
|
# get the well segments sorted in ascending order
|
|
@@ -341,7 +344,7 @@ def _find_volume_in_partial_frustum(
|
|
|
341
344
|
) -> float:
|
|
342
345
|
"""Look through a sorted list of frusta for a target height, and find the volume at that height."""
|
|
343
346
|
for segment in sorted_well:
|
|
344
|
-
if segment.bottomHeight
|
|
347
|
+
if segment.bottomHeight <= target_height <= segment.topHeight:
|
|
345
348
|
relative_target_height = target_height - segment.bottomHeight
|
|
346
349
|
section_height = segment.topHeight - segment.bottomHeight
|
|
347
350
|
return volume_at_height_within_section(
|
|
@@ -356,9 +359,13 @@ def _find_volume_in_partial_frustum(
|
|
|
356
359
|
|
|
357
360
|
|
|
358
361
|
def find_volume_at_well_height(
|
|
359
|
-
target_height:
|
|
360
|
-
|
|
362
|
+
target_height: LiquidTrackingType,
|
|
363
|
+
well_geometry: InnerWellGeometry,
|
|
364
|
+
) -> LiquidTrackingType:
|
|
361
365
|
"""Find the volume within a well, at a known height."""
|
|
366
|
+
# comparisons with SimulatedProbeResult objects aren't meaningful, just return
|
|
367
|
+
if isinstance(target_height, SimulatedProbeResult):
|
|
368
|
+
return target_height
|
|
362
369
|
volumetric_capacity = get_well_volumetric_capacity(well_geometry)
|
|
363
370
|
max_height = volumetric_capacity[-1][0]
|
|
364
371
|
if target_height < 0 or target_height > max_height:
|
|
@@ -417,13 +424,22 @@ def _find_height_in_partial_frustum(
|
|
|
417
424
|
|
|
418
425
|
|
|
419
426
|
def find_height_at_well_volume(
|
|
420
|
-
target_volume:
|
|
421
|
-
|
|
427
|
+
target_volume: LiquidTrackingType,
|
|
428
|
+
well_geometry: InnerWellGeometry,
|
|
429
|
+
raise_error_if_result_invalid: bool = True,
|
|
430
|
+
) -> LiquidTrackingType:
|
|
422
431
|
"""Find the height within a well, at a known volume."""
|
|
432
|
+
# comparisons with SimulatedProbeResult objects aren't meaningful, just
|
|
433
|
+
# return if we have one of those
|
|
434
|
+
if isinstance(target_volume, SimulatedProbeResult):
|
|
435
|
+
return target_volume
|
|
436
|
+
|
|
423
437
|
volumetric_capacity = get_well_volumetric_capacity(well_geometry)
|
|
424
438
|
max_volume = sum(row[1] for row in volumetric_capacity)
|
|
425
|
-
|
|
426
|
-
|
|
439
|
+
|
|
440
|
+
if raise_error_if_result_invalid:
|
|
441
|
+
if target_volume < 0 or target_volume > max_volume:
|
|
442
|
+
raise InvalidLiquidHeightFound("Invalid target volume.")
|
|
427
443
|
|
|
428
444
|
sorted_well = sorted(well_geometry.sections, key=lambda section: section.topHeight)
|
|
429
445
|
# find the section the target volume is in and compute the height
|