opentrons 8.3.2__py2.py3-none-any.whl → 8.4.0__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/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 +102 -5
- opentrons/legacy_commands/helpers.py +74 -1
- opentrons/legacy_commands/types.py +33 -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 +1356 -107
- 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/pipette_movement_conflict.py +6 -14
- 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 +858 -0
- opentrons/protocol_api/core/engine/well.py +73 -5
- opentrons/protocol_api/core/instrument.py +71 -21
- 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 +76 -49
- 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 +27 -2
- 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 +73 -23
- opentrons/protocol_api/core/module.py +43 -0
- opentrons/protocol_api/core/protocol.py +33 -0
- opentrons/protocol_api/core/well.py +23 -2
- opentrons/protocol_api/instrument_context.py +454 -150
- opentrons/protocol_api/labware.py +98 -50
- opentrons/protocol_api/module_contexts.py +140 -0
- opentrons/protocol_api/protocol_context.py +163 -19
- 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 +66 -36
- 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 +210 -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 +102 -33
- 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 +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +23 -1
- 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 +291 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +31 -2
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +27 -13
- 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/move_to_well.py +5 -11
- opentrons/protocol_engine/commands/pick_up_tip.py +5 -2
- opentrons/protocol_engine/commands/pipetting_common.py +159 -8
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +15 -5
- opentrons/protocol_engine/commands/{evotip_dispense.py → pressure_dispense.py} +33 -34
- opentrons/protocol_engine/commands/reload_labware.py +6 -19
- opentrons/protocol_engine/commands/{evotip_seal_pipette.py → seal_pipette_to_tip.py} +97 -76
- 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/commands/{evotip_unseal_pipette.py → unseal_pipette_from_tip.py} +31 -40
- opentrons/protocol_engine/errors/__init__.py +10 -0
- opentrons/protocol_engine/errors/exceptions.py +62 -0
- opentrons/protocol_engine/execution/equipment.py +123 -106
- opentrons/protocol_engine/execution/labware_movement.py +8 -6
- opentrons/protocol_engine/execution/pipetting.py +235 -25
- opentrons/protocol_engine/execution/tip_handler.py +82 -32
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/protocol_engine.py +22 -13
- opentrons/protocol_engine/resources/deck_configuration_provider.py +98 -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 +36 -14
- opentrons/protocol_engine/state/geometry.py +892 -227
- 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 +210 -67
- 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 +55 -9
- opentrons/protocol_engine/types/__init__.py +300 -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 +111 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -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 +131 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +301 -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 +124 -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 +27 -23
- opentrons/protocols/models/__init__.py +0 -21
- opentrons/simulate.py +4 -2
- opentrons/types.py +20 -7
- opentrons/util/logging_config.py +94 -25
- opentrons/util/logging_queue_handler.py +61 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/METADATA +4 -4
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/RECORD +192 -151
- 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.2.dist-info → opentrons-8.4.0.dist-info}/LICENSE +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/WHEEL +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.2.dist-info → opentrons-8.4.0.dist-info}/top_level.txt +0 -0
|
@@ -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(
|
|
@@ -350,19 +353,26 @@ def _find_volume_in_partial_frustum(
|
|
|
350
353
|
section_height=section_height,
|
|
351
354
|
)
|
|
352
355
|
# if we've looked through all sections and can't find the target volume, raise an error
|
|
356
|
+
# this code should never be reached- an error should be raised by find_volume_at_well_height
|
|
353
357
|
raise InvalidLiquidHeightFound(
|
|
354
|
-
f"
|
|
358
|
+
f"Target height {target_height} mm exceeds the well height."
|
|
355
359
|
)
|
|
356
360
|
|
|
357
361
|
|
|
358
362
|
def find_volume_at_well_height(
|
|
359
|
-
target_height:
|
|
360
|
-
|
|
363
|
+
target_height: LiquidTrackingType,
|
|
364
|
+
well_geometry: InnerWellGeometry,
|
|
365
|
+
) -> LiquidTrackingType:
|
|
361
366
|
"""Find the volume within a well, at a known height."""
|
|
367
|
+
# comparisons with SimulatedProbeResult objects aren't meaningful, just return
|
|
368
|
+
if isinstance(target_height, SimulatedProbeResult):
|
|
369
|
+
return target_height
|
|
362
370
|
volumetric_capacity = get_well_volumetric_capacity(well_geometry)
|
|
363
371
|
max_height = volumetric_capacity[-1][0]
|
|
364
372
|
if target_height < 0 or target_height > max_height:
|
|
365
|
-
raise InvalidLiquidHeightFound(
|
|
373
|
+
raise InvalidLiquidHeightFound(
|
|
374
|
+
f"Invalid target height {target_height} mm; max well height is {max_height} mm."
|
|
375
|
+
)
|
|
366
376
|
# volumes in volumetric_capacity are relative to each frustum,
|
|
367
377
|
# so we have to find the volume of all the full sections enclosed
|
|
368
378
|
# beneath the target height
|
|
@@ -395,7 +405,7 @@ def _find_height_in_partial_frustum(
|
|
|
395
405
|
section_top_height, section_volume = capacity
|
|
396
406
|
if (
|
|
397
407
|
bottom_section_volume
|
|
398
|
-
|
|
408
|
+
<= target_volume
|
|
399
409
|
<= (bottom_section_volume + section_volume)
|
|
400
410
|
):
|
|
401
411
|
relative_target_volume = target_volume - bottom_section_volume
|
|
@@ -410,21 +420,33 @@ def _find_height_in_partial_frustum(
|
|
|
410
420
|
# viewed section
|
|
411
421
|
bottom_section_volume += section_volume
|
|
412
422
|
|
|
423
|
+
# if we finish looping through the whole well, bottom_section will be the well's volume
|
|
424
|
+
total_well_volume = bottom_section_volume
|
|
413
425
|
# if we've looked through all sections and can't find the target volume, raise an error
|
|
426
|
+
# also this code should never be reached bc an invalid target volume should be changed
|
|
427
|
+
# by find_height_at_well_volume
|
|
414
428
|
raise InvalidLiquidHeightFound(
|
|
415
|
-
f"
|
|
429
|
+
f"Target volume {target_volume} uL exceeds the well volume {total_well_volume} uL."
|
|
416
430
|
)
|
|
417
431
|
|
|
418
432
|
|
|
419
433
|
def find_height_at_well_volume(
|
|
420
|
-
target_volume:
|
|
421
|
-
|
|
434
|
+
target_volume: LiquidTrackingType,
|
|
435
|
+
well_geometry: InnerWellGeometry,
|
|
436
|
+
) -> LiquidTrackingType:
|
|
422
437
|
"""Find the height within a well, at a known volume."""
|
|
438
|
+
# comparisons with SimulatedProbeResult objects aren't meaningful, just
|
|
439
|
+
# return if we have one of those
|
|
440
|
+
if isinstance(target_volume, SimulatedProbeResult):
|
|
441
|
+
return target_volume
|
|
442
|
+
|
|
423
443
|
volumetric_capacity = get_well_volumetric_capacity(well_geometry)
|
|
424
444
|
max_volume = sum(row[1] for row in volumetric_capacity)
|
|
425
|
-
if target_volume < 0 or target_volume > max_volume:
|
|
426
|
-
raise InvalidLiquidHeightFound("Invalid target volume.")
|
|
427
445
|
|
|
446
|
+
if target_volume < 0:
|
|
447
|
+
target_volume = 0
|
|
448
|
+
elif target_volume > max_volume:
|
|
449
|
+
target_volume = max_volume
|
|
428
450
|
sorted_well = sorted(well_geometry.sections, key=lambda section: section.topHeight)
|
|
429
451
|
# find the section the target volume is in and compute the height
|
|
430
452
|
return _find_height_in_partial_frustum(
|