opentrons 8.2.0a3__py2.py3-none-any.whl → 8.3.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.
- opentrons/calibration_storage/deck_configuration.py +3 -3
- opentrons/calibration_storage/file_operators.py +3 -3
- opentrons/calibration_storage/helpers.py +3 -1
- opentrons/calibration_storage/ot2/models/v1.py +16 -29
- opentrons/calibration_storage/ot2/tip_length.py +7 -4
- opentrons/calibration_storage/ot3/models/v1.py +14 -23
- opentrons/cli/analyze.py +18 -6
- opentrons/config/defaults_ot3.py +1 -0
- opentrons/drivers/asyncio/communication/__init__.py +2 -0
- opentrons/drivers/asyncio/communication/errors.py +16 -3
- opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
- opentrons/drivers/command_builder.py +2 -2
- opentrons/drivers/flex_stacker/__init__.py +9 -0
- opentrons/drivers/flex_stacker/abstract.py +89 -0
- opentrons/drivers/flex_stacker/driver.py +260 -0
- opentrons/drivers/flex_stacker/simulator.py +109 -0
- opentrons/drivers/flex_stacker/types.py +138 -0
- opentrons/drivers/heater_shaker/driver.py +18 -3
- opentrons/drivers/temp_deck/driver.py +13 -3
- opentrons/drivers/thermocycler/driver.py +17 -3
- opentrons/execute.py +3 -1
- opentrons/hardware_control/__init__.py +1 -2
- opentrons/hardware_control/api.py +33 -21
- opentrons/hardware_control/backends/flex_protocol.py +17 -7
- opentrons/hardware_control/backends/ot3controller.py +213 -63
- opentrons/hardware_control/backends/ot3simulator.py +18 -9
- opentrons/hardware_control/backends/ot3utils.py +43 -15
- opentrons/hardware_control/dev_types.py +4 -0
- opentrons/hardware_control/emulation/heater_shaker.py +4 -0
- opentrons/hardware_control/emulation/module_server/client.py +1 -1
- opentrons/hardware_control/emulation/module_server/server.py +5 -3
- opentrons/hardware_control/emulation/settings.py +3 -4
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
- opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
- opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
- opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
- opentrons/hardware_control/modules/mod_abc.py +2 -2
- opentrons/hardware_control/motion_utilities.py +68 -0
- opentrons/hardware_control/nozzle_manager.py +39 -41
- opentrons/hardware_control/ot3_calibration.py +1 -1
- opentrons/hardware_control/ot3api.py +78 -31
- opentrons/hardware_control/protocols/gripper_controller.py +3 -0
- opentrons/hardware_control/protocols/hardware_manager.py +5 -1
- opentrons/hardware_control/protocols/liquid_handler.py +22 -1
- opentrons/hardware_control/protocols/motion_controller.py +7 -0
- opentrons/hardware_control/robot_calibration.py +1 -1
- opentrons/hardware_control/types.py +61 -0
- opentrons/legacy_commands/commands.py +37 -0
- opentrons/legacy_commands/types.py +39 -0
- opentrons/protocol_api/__init__.py +20 -1
- opentrons/protocol_api/_liquid.py +24 -49
- opentrons/protocol_api/_liquid_properties.py +754 -0
- opentrons/protocol_api/_types.py +24 -0
- opentrons/protocol_api/core/common.py +2 -0
- opentrons/protocol_api/core/engine/instrument.py +191 -10
- opentrons/protocol_api/core/engine/labware.py +29 -7
- opentrons/protocol_api/core/engine/protocol.py +130 -5
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/well.py +4 -1
- opentrons/protocol_api/core/instrument.py +73 -4
- opentrons/protocol_api/core/labware.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
- opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
- opentrons/protocol_api/core/protocol.py +34 -1
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/instrument_context.py +299 -44
- opentrons/protocol_api/labware.py +248 -9
- opentrons/protocol_api/module_contexts.py +21 -17
- opentrons/protocol_api/protocol_context.py +125 -4
- opentrons/protocol_api/robot_context.py +204 -32
- opentrons/protocol_api/validation.py +262 -3
- opentrons/protocol_engine/__init__.py +4 -0
- opentrons/protocol_engine/actions/actions.py +2 -3
- opentrons/protocol_engine/clients/sync_client.py +18 -0
- opentrons/protocol_engine/commands/__init__.py +121 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
- opentrons/protocol_engine/commands/absorbance_reader/read.py +40 -10
- opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
- opentrons/protocol_engine/commands/aspirate.py +103 -53
- opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
- opentrons/protocol_engine/commands/blow_out.py +44 -39
- opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
- opentrons/protocol_engine/commands/command.py +73 -66
- opentrons/protocol_engine/commands/command_unions.py +140 -1
- opentrons/protocol_engine/commands/comment.py +1 -1
- opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
- opentrons/protocol_engine/commands/custom.py +6 -12
- opentrons/protocol_engine/commands/dispense.py +82 -48
- opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
- opentrons/protocol_engine/commands/drop_tip.py +52 -31
- opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
- opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
- opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
- opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/home.py +13 -4
- opentrons/protocol_engine/commands/liquid_probe.py +125 -31
- opentrons/protocol_engine/commands/load_labware.py +33 -6
- opentrons/protocol_engine/commands/load_lid.py +146 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
- opentrons/protocol_engine/commands/load_liquid.py +12 -4
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +31 -10
- opentrons/protocol_engine/commands/load_pipette.py +19 -8
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
- opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
- opentrons/protocol_engine/commands/move_labware.py +28 -6
- opentrons/protocol_engine/commands/move_relative.py +35 -25
- opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
- opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
- opentrons/protocol_engine/commands/move_to_well.py +40 -24
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
- opentrons/protocol_engine/commands/pipetting_common.py +169 -87
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
- opentrons/protocol_engine/commands/reload_labware.py +1 -1
- opentrons/protocol_engine/commands/retract_axis.py +1 -1
- opentrons/protocol_engine/commands/robot/__init__.py +69 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
- opentrons/protocol_engine/commands/robot/common.py +18 -0
- opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
- opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
- opentrons/protocol_engine/commands/robot/move_to.py +94 -0
- opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
- opentrons/protocol_engine/commands/save_position.py +14 -5
- opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
- opentrons/protocol_engine/commands/set_status_bar.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
- opentrons/protocol_engine/commands/touch_tip.py +65 -16
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +4 -2
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
- opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
- opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
- opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
- opentrons/protocol_engine/errors/__init__.py +12 -0
- opentrons/protocol_engine/errors/error_occurrence.py +19 -20
- opentrons/protocol_engine/errors/exceptions.py +76 -0
- opentrons/protocol_engine/execution/command_executor.py +1 -1
- opentrons/protocol_engine/execution/equipment.py +73 -5
- opentrons/protocol_engine/execution/gantry_mover.py +369 -8
- opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
- opentrons/protocol_engine/execution/movement.py +27 -0
- opentrons/protocol_engine/execution/pipetting.py +5 -1
- opentrons/protocol_engine/execution/tip_handler.py +34 -15
- opentrons/protocol_engine/notes/notes.py +1 -1
- opentrons/protocol_engine/protocol_engine.py +7 -6
- opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
- opentrons/protocol_engine/resources/labware_validation.py +18 -0
- opentrons/protocol_engine/resources/module_data_provider.py +1 -1
- opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
- opentrons/protocol_engine/slot_standardization.py +9 -9
- opentrons/protocol_engine/state/_move_types.py +9 -5
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +25 -61
- opentrons/protocol_engine/state/command_history.py +12 -0
- opentrons/protocol_engine/state/commands.py +22 -14
- opentrons/protocol_engine/state/files.py +10 -12
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/frustum_helpers.py +63 -69
- opentrons/protocol_engine/state/geometry.py +47 -1
- opentrons/protocol_engine/state/labware.py +92 -26
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +16 -4
- opentrons/protocol_engine/state/modules.py +56 -71
- opentrons/protocol_engine/state/motion.py +6 -1
- opentrons/protocol_engine/state/pipettes.py +149 -58
- opentrons/protocol_engine/state/state.py +21 -2
- opentrons/protocol_engine/state/state_summary.py +4 -2
- opentrons/protocol_engine/state/tips.py +11 -44
- opentrons/protocol_engine/state/update_types.py +343 -48
- opentrons/protocol_engine/state/wells.py +19 -11
- opentrons/protocol_engine/types.py +176 -28
- opentrons/protocol_reader/extract_labware_definitions.py +5 -2
- opentrons/protocol_reader/file_format_validator.py +5 -5
- opentrons/protocol_runner/json_file_reader.py +9 -3
- opentrons/protocol_runner/json_translator.py +51 -25
- opentrons/protocol_runner/legacy_command_mapper.py +66 -64
- opentrons/protocol_runner/protocol_runner.py +35 -4
- opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
- opentrons/protocol_runner/run_orchestrator.py +13 -3
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +1 -1
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +56 -0
- opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/instrument.py +1 -1
- opentrons/protocols/api_support/util.py +10 -0
- opentrons/protocols/labware.py +70 -8
- opentrons/protocols/models/json_protocol.py +5 -9
- opentrons/simulate.py +3 -1
- opentrons/types.py +162 -2
- opentrons/util/entrypoint_util.py +2 -5
- opentrons/util/logging_config.py +1 -1
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
|
@@ -35,7 +35,7 @@ def make_error_recovery_debug_note(type: "ErrorRecoveryType") -> CommandNote:
|
|
|
35
35
|
This is intended to be read by developers and support people, not computers.
|
|
36
36
|
"""
|
|
37
37
|
message = f"Handling this command failure with {type.name}."
|
|
38
|
-
return CommandNote.
|
|
38
|
+
return CommandNote.model_construct(
|
|
39
39
|
noteKind="debugErrorRecovery",
|
|
40
40
|
shortMessage=message,
|
|
41
41
|
longMessage=message,
|
|
@@ -30,7 +30,6 @@ from .types import (
|
|
|
30
30
|
HexColor,
|
|
31
31
|
PostRunHardwareState,
|
|
32
32
|
DeckConfigurationType,
|
|
33
|
-
AddressableAreaLocation,
|
|
34
33
|
)
|
|
35
34
|
from .execution import (
|
|
36
35
|
QueueWorker,
|
|
@@ -427,7 +426,7 @@ class ProtocolEngine:
|
|
|
427
426
|
post_run_hardware_state: The state in which to leave the gantry and motors in
|
|
428
427
|
after the run is over.
|
|
429
428
|
"""
|
|
430
|
-
if self._state_store.commands.
|
|
429
|
+
if self._state_store.commands.get_is_stopped_by_estop():
|
|
431
430
|
# This handles the case where the E-stop was pressed while we were *not* in the middle
|
|
432
431
|
# of some hardware interaction that would raise it as an exception. For example, imagine
|
|
433
432
|
# we were paused between two commands, or imagine we were executing a waitForDuration.
|
|
@@ -565,15 +564,17 @@ class ProtocolEngine:
|
|
|
565
564
|
description=(description or ""),
|
|
566
565
|
displayColor=color,
|
|
567
566
|
)
|
|
567
|
+
validated_liquid = self._state_store.liquid.validate_liquid_allowed(
|
|
568
|
+
liquid=liquid
|
|
569
|
+
)
|
|
568
570
|
|
|
569
|
-
self._action_dispatcher.dispatch(AddLiquidAction(liquid=
|
|
570
|
-
return
|
|
571
|
+
self._action_dispatcher.dispatch(AddLiquidAction(liquid=validated_liquid))
|
|
572
|
+
return validated_liquid
|
|
571
573
|
|
|
572
574
|
def add_addressable_area(self, addressable_area_name: str) -> None:
|
|
573
575
|
"""Add an addressable area to state."""
|
|
574
|
-
area = AddressableAreaLocation(addressableAreaName=addressable_area_name)
|
|
575
576
|
self._action_dispatcher.dispatch(
|
|
576
|
-
AddAddressableAreaAction(
|
|
577
|
+
AddAddressableAreaAction(addressable_area_name)
|
|
577
578
|
)
|
|
578
579
|
|
|
579
580
|
def reset_tips(self, labware_id: str) -> None:
|
|
@@ -44,7 +44,7 @@ class LabwareDataProvider:
|
|
|
44
44
|
def _get_labware_definition_sync(
|
|
45
45
|
load_name: str, namespace: str, version: int
|
|
46
46
|
) -> LabwareDefinition:
|
|
47
|
-
return LabwareDefinition.
|
|
47
|
+
return LabwareDefinition.model_validate(
|
|
48
48
|
get_labware_definition(load_name, namespace, version)
|
|
49
49
|
)
|
|
50
50
|
|
|
@@ -14,6 +14,11 @@ def is_absorbance_reader_lid(load_name: str) -> bool:
|
|
|
14
14
|
return load_name == "opentrons_flex_lid_absorbance_plate_reader_module"
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def is_evotips(load_name: str) -> bool:
|
|
18
|
+
"""Check if a labware is an evotips tiprack."""
|
|
19
|
+
return load_name == "evotips_opentrons_96_labware"
|
|
20
|
+
|
|
21
|
+
|
|
17
22
|
def validate_definition_is_labware(definition: LabwareDefinition) -> bool:
|
|
18
23
|
"""Validate that one of the definition's allowed roles is `labware`.
|
|
19
24
|
|
|
@@ -32,6 +37,11 @@ def validate_definition_is_lid(definition: LabwareDefinition) -> bool:
|
|
|
32
37
|
return LabwareRole.lid in definition.allowedRoles
|
|
33
38
|
|
|
34
39
|
|
|
40
|
+
def validate_definition_is_system(definition: LabwareDefinition) -> bool:
|
|
41
|
+
"""Validate that one of the definition's allowed roles is `system`."""
|
|
42
|
+
return LabwareRole.system in definition.allowedRoles
|
|
43
|
+
|
|
44
|
+
|
|
35
45
|
def validate_labware_can_be_stacked(
|
|
36
46
|
top_labware_definition: LabwareDefinition, below_labware_load_name: str
|
|
37
47
|
) -> bool:
|
|
@@ -39,6 +49,14 @@ def validate_labware_can_be_stacked(
|
|
|
39
49
|
return below_labware_load_name in top_labware_definition.stackingOffsetWithLabware
|
|
40
50
|
|
|
41
51
|
|
|
52
|
+
def validate_labware_can_be_ondeck(definition: LabwareDefinition) -> bool:
|
|
53
|
+
"""Validate that the labware being loaded onto the deck can sit in a slot."""
|
|
54
|
+
return (
|
|
55
|
+
definition.parameters.quirks is None
|
|
56
|
+
or "stackingOnly" not in definition.parameters.quirks
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
42
60
|
def validate_gripper_compatible(definition: LabwareDefinition) -> bool:
|
|
43
61
|
"""Validate that the labware definition does not have a quirk disallowing movement with gripper."""
|
|
44
62
|
return (
|
|
@@ -22,7 +22,7 @@ class ModuleDataProvider:
|
|
|
22
22
|
def get_definition(model: ModuleModel) -> ModuleDefinition:
|
|
23
23
|
"""Get the module definition."""
|
|
24
24
|
data = load_definition(model_or_loadname=model.value, version="3")
|
|
25
|
-
return ModuleDefinition.
|
|
25
|
+
return ModuleDefinition.model_validate(data)
|
|
26
26
|
|
|
27
27
|
@staticmethod
|
|
28
28
|
def load_module_calibrations() -> Dict[str, ModuleOffsetData]:
|
|
@@ -67,6 +67,9 @@ class LoadedStaticPipetteData:
|
|
|
67
67
|
back_left_corner_offset: Point
|
|
68
68
|
front_right_corner_offset: Point
|
|
69
69
|
pipette_lld_settings: Optional[Dict[str, Dict[str, float]]]
|
|
70
|
+
plunger_positions: Dict[str, float]
|
|
71
|
+
shaft_ul_per_mm: float
|
|
72
|
+
available_sensors: pipette_definition.AvailableSensorDefinition
|
|
70
73
|
|
|
71
74
|
|
|
72
75
|
class VirtualPipetteDataProvider:
|
|
@@ -95,6 +98,7 @@ class VirtualPipetteDataProvider:
|
|
|
95
98
|
config.pipette_type,
|
|
96
99
|
config.channels,
|
|
97
100
|
config.version,
|
|
101
|
+
pip_types.PipetteOEMType.OT,
|
|
98
102
|
)
|
|
99
103
|
new_nozzle_manager = NozzleConfigurationManager.build_from_config(
|
|
100
104
|
config, valid_nozzle_maps
|
|
@@ -127,6 +131,7 @@ class VirtualPipetteDataProvider:
|
|
|
127
131
|
pipette_model.pipette_type,
|
|
128
132
|
pipette_model.pipette_channels,
|
|
129
133
|
pipette_model.pipette_version,
|
|
134
|
+
pipette_model.oem_type,
|
|
130
135
|
)
|
|
131
136
|
|
|
132
137
|
liquid_class = pipette_definition.liquid_class_for_volume_between_default_and_defaultlowvolume(
|
|
@@ -160,6 +165,7 @@ class VirtualPipetteDataProvider:
|
|
|
160
165
|
pipette_model.pipette_type,
|
|
161
166
|
pipette_model.pipette_channels,
|
|
162
167
|
pipette_model.pipette_version,
|
|
168
|
+
pipette_model.oem_type,
|
|
163
169
|
)
|
|
164
170
|
|
|
165
171
|
def _get_virtual_pipette_static_config_by_model( # noqa: C901
|
|
@@ -176,6 +182,7 @@ class VirtualPipetteDataProvider:
|
|
|
176
182
|
pipette_model.pipette_type,
|
|
177
183
|
pipette_model.pipette_channels,
|
|
178
184
|
pipette_model.pipette_version,
|
|
185
|
+
pipette_model.oem_type,
|
|
179
186
|
)
|
|
180
187
|
try:
|
|
181
188
|
tip_type = pip_types.PipetteTipType(
|
|
@@ -192,6 +199,7 @@ class VirtualPipetteDataProvider:
|
|
|
192
199
|
pipette_model.pipette_type,
|
|
193
200
|
pipette_model.pipette_channels,
|
|
194
201
|
pipette_model.pipette_version,
|
|
202
|
+
pipette_model.oem_type,
|
|
195
203
|
)
|
|
196
204
|
if pipette_id not in self._nozzle_manager_layout_by_id:
|
|
197
205
|
nozzle_manager = NozzleConfigurationManager.build_from_config(
|
|
@@ -252,6 +260,7 @@ class VirtualPipetteDataProvider:
|
|
|
252
260
|
|
|
253
261
|
pip_back_left = config.pipette_bounding_box_offsets.back_left_corner
|
|
254
262
|
pip_front_right = config.pipette_bounding_box_offsets.front_right_corner
|
|
263
|
+
plunger_positions = config.plunger_positions_configurations[liquid_class]
|
|
255
264
|
return LoadedStaticPipetteData(
|
|
256
265
|
model=str(pipette_model),
|
|
257
266
|
display_name=config.display_name,
|
|
@@ -280,6 +289,15 @@ class VirtualPipetteDataProvider:
|
|
|
280
289
|
pip_front_right[0], pip_front_right[1], pip_front_right[2]
|
|
281
290
|
),
|
|
282
291
|
pipette_lld_settings=config.lld_settings,
|
|
292
|
+
plunger_positions={
|
|
293
|
+
"top": plunger_positions.top,
|
|
294
|
+
"bottom": plunger_positions.bottom,
|
|
295
|
+
"blow_out": plunger_positions.blow_out,
|
|
296
|
+
"drop_tip": plunger_positions.drop_tip,
|
|
297
|
+
},
|
|
298
|
+
shaft_ul_per_mm=config.shaft_ul_per_mm,
|
|
299
|
+
available_sensors=config.available_sensors
|
|
300
|
+
or pipette_definition.AvailableSensorDefinition(sensors=[]),
|
|
283
301
|
)
|
|
284
302
|
|
|
285
303
|
def get_virtual_pipette_static_config(
|
|
@@ -298,6 +316,11 @@ def get_pipette_static_config(
|
|
|
298
316
|
"""Get the config for a pipette, given the state/config object from the HW API."""
|
|
299
317
|
back_left_offset = pipette_dict["pipette_bounding_box_offsets"].back_left_corner
|
|
300
318
|
front_right_offset = pipette_dict["pipette_bounding_box_offsets"].front_right_corner
|
|
319
|
+
available_sensors = (
|
|
320
|
+
pipette_dict["available_sensors"]
|
|
321
|
+
if "available_sensors" in pipette_dict.keys()
|
|
322
|
+
else pipette_definition.AvailableSensorDefinition(sensors=[])
|
|
323
|
+
)
|
|
301
324
|
return LoadedStaticPipetteData(
|
|
302
325
|
model=pipette_dict["model"],
|
|
303
326
|
display_name=pipette_dict["display_name"],
|
|
@@ -327,6 +350,9 @@ def get_pipette_static_config(
|
|
|
327
350
|
front_right_offset[0], front_right_offset[1], front_right_offset[2]
|
|
328
351
|
),
|
|
329
352
|
pipette_lld_settings=pipette_dict["lld_settings"],
|
|
353
|
+
plunger_positions=pipette_dict["plunger_positions"],
|
|
354
|
+
shaft_ul_per_mm=pipette_dict["shaft_ul_per_mm"],
|
|
355
|
+
available_sensors=available_sensors,
|
|
330
356
|
)
|
|
331
357
|
|
|
332
358
|
|
|
@@ -35,9 +35,9 @@ def standardize_labware_offset(
|
|
|
35
35
|
original: LabwareOffsetCreate, robot_type: RobotType
|
|
36
36
|
) -> LabwareOffsetCreate:
|
|
37
37
|
"""Convert the deck slot in the given `LabwareOffsetCreate` to match the given robot type."""
|
|
38
|
-
return original.
|
|
38
|
+
return original.model_copy(
|
|
39
39
|
update={
|
|
40
|
-
"location": original.location.
|
|
40
|
+
"location": original.location.model_copy(
|
|
41
41
|
update={
|
|
42
42
|
"slotName": original.location.slotName.to_equivalent_for_robot_type(
|
|
43
43
|
robot_type
|
|
@@ -70,40 +70,40 @@ def standardize_command(
|
|
|
70
70
|
def _standardize_load_labware(
|
|
71
71
|
original: commands.LoadLabwareCreate, robot_type: RobotType
|
|
72
72
|
) -> commands.LoadLabwareCreate:
|
|
73
|
-
params = original.params.
|
|
73
|
+
params = original.params.model_copy(
|
|
74
74
|
update={
|
|
75
75
|
"location": _standardize_labware_location(
|
|
76
76
|
original.params.location, robot_type
|
|
77
77
|
)
|
|
78
78
|
}
|
|
79
79
|
)
|
|
80
|
-
return original.
|
|
80
|
+
return original.model_copy(update={"params": params})
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
def _standardize_load_module(
|
|
84
84
|
original: commands.LoadModuleCreate, robot_type: RobotType
|
|
85
85
|
) -> commands.LoadModuleCreate:
|
|
86
|
-
params = original.params.
|
|
86
|
+
params = original.params.model_copy(
|
|
87
87
|
update={
|
|
88
88
|
"location": _standardize_deck_slot_location(
|
|
89
89
|
original.params.location, robot_type
|
|
90
90
|
)
|
|
91
91
|
}
|
|
92
92
|
)
|
|
93
|
-
return original.
|
|
93
|
+
return original.model_copy(update={"params": params})
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
def _standardize_move_labware(
|
|
97
97
|
original: commands.MoveLabwareCreate, robot_type: RobotType
|
|
98
98
|
) -> commands.MoveLabwareCreate:
|
|
99
|
-
params = original.params.
|
|
99
|
+
params = original.params.model_copy(
|
|
100
100
|
update={
|
|
101
101
|
"newLocation": _standardize_labware_location(
|
|
102
102
|
original.params.newLocation, robot_type
|
|
103
103
|
)
|
|
104
104
|
}
|
|
105
105
|
)
|
|
106
|
-
return original.
|
|
106
|
+
return original.model_copy(update={"params": params})
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
_standardize_command_functions: Dict[
|
|
@@ -135,6 +135,6 @@ def _standardize_labware_location(
|
|
|
135
135
|
def _standardize_deck_slot_location(
|
|
136
136
|
original: DeckSlotLocation, robot_type: RobotType
|
|
137
137
|
) -> DeckSlotLocation:
|
|
138
|
-
return original.
|
|
138
|
+
return original.model_copy(
|
|
139
139
|
update={"slotName": original.slotName.to_equivalent_for_robot_type(robot_type)}
|
|
140
140
|
)
|
|
@@ -53,15 +53,19 @@ def get_move_type_to_well(
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def get_edge_point_list(
|
|
56
|
-
center: Point,
|
|
56
|
+
center: Point,
|
|
57
|
+
x_radius: float,
|
|
58
|
+
y_radius: float,
|
|
59
|
+
mm_from_edge: float,
|
|
60
|
+
edge_path_type: EdgePathType,
|
|
57
61
|
) -> List[Point]:
|
|
58
62
|
"""Get list of edge points dependent on edge path type."""
|
|
59
63
|
edges = EdgeList(
|
|
60
|
-
right=center + Point(x=x_radius, y=0, z=0),
|
|
61
|
-
left=center + Point(x=-x_radius, y=0, z=0),
|
|
64
|
+
right=center + Point(x=x_radius - mm_from_edge, y=0, z=0),
|
|
65
|
+
left=center + Point(x=-x_radius + mm_from_edge, y=0, z=0),
|
|
62
66
|
center=center,
|
|
63
|
-
forward=center + Point(x=0, y=y_radius, z=0),
|
|
64
|
-
back=center + Point(x=0, y=-y_radius, z=0),
|
|
67
|
+
forward=center + Point(x=0, y=y_radius - mm_from_edge, z=0),
|
|
68
|
+
back=center + Point(x=0, y=-y_radius + mm_from_edge, z=0),
|
|
65
69
|
)
|
|
66
70
|
|
|
67
71
|
if edge_path_type == EdgePathType.LEFT:
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Utilities for doing coverage math on wells."""
|
|
2
|
+
|
|
3
|
+
from typing import Iterator
|
|
4
|
+
from opentrons_shared_data.errors.exceptions import (
|
|
5
|
+
InvalidStoredData,
|
|
6
|
+
InvalidProtocolData,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from opentrons.hardware_control.nozzle_manager import NozzleMap
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def wells_covered_by_pipette_configuration(
|
|
13
|
+
nozzle_map: NozzleMap,
|
|
14
|
+
target_well: str,
|
|
15
|
+
labware_wells_by_column: list[list[str]],
|
|
16
|
+
) -> Iterator[str]:
|
|
17
|
+
"""Compute the wells covered by a pipette nozzle configuration."""
|
|
18
|
+
if len(labware_wells_by_column) >= 12 and len(labware_wells_by_column[0]) >= 8:
|
|
19
|
+
yield from wells_covered_dense(
|
|
20
|
+
nozzle_map,
|
|
21
|
+
target_well,
|
|
22
|
+
labware_wells_by_column,
|
|
23
|
+
)
|
|
24
|
+
elif len(labware_wells_by_column) < 12 and len(labware_wells_by_column[0]) < 8:
|
|
25
|
+
yield from wells_covered_sparse(
|
|
26
|
+
nozzle_map, target_well, labware_wells_by_column
|
|
27
|
+
)
|
|
28
|
+
else:
|
|
29
|
+
raise InvalidStoredData(
|
|
30
|
+
"Labware of non-SBS and non-reservoir format cannot be handled"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def row_col_ordinals_from_column_major_map(
|
|
35
|
+
target_well: str, column_major_wells: list[list[str]]
|
|
36
|
+
) -> tuple[int, int]:
|
|
37
|
+
"""Turn a well name into the index of its row and column (in that order) within the labware."""
|
|
38
|
+
for column_index, column in enumerate(column_major_wells):
|
|
39
|
+
if target_well in column:
|
|
40
|
+
return column.index(target_well), column_index
|
|
41
|
+
raise InvalidStoredData(f"Well name {target_well} is not present in labware")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def wells_covered_dense( # noqa: C901
|
|
45
|
+
nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]]
|
|
46
|
+
) -> Iterator[str]:
|
|
47
|
+
"""Get the list of wells covered by a nozzle map on an SBS format labware with a specified multiplier of 96 into the number of wells.
|
|
48
|
+
|
|
49
|
+
This will handle the offsetting of the nozzle map into higher-density well plates. For instance, a full column config target at A1 of a
|
|
50
|
+
96 plate would cover wells A1, B1, C1, D1, E1, F1, G1, H1, and use downsample_factor 1.0 (96*1 = 96). A full column config target on a
|
|
51
|
+
384 plate would cover wells A1, C1, E1, G1, I1, K1, M1, O1 and use downsample_factor 4.0 (96*4 = 384), while a full column config
|
|
52
|
+
targeting B1 would cover wells B1, D1, F1, H1, J1, L1, N1, P1 - still using downsample_factor 4.0, with the offset gathered from the
|
|
53
|
+
target well.
|
|
54
|
+
|
|
55
|
+
The function may also handle sub-96 regular labware with fractional downsample factors, but that's physically improbable and it's not
|
|
56
|
+
tested. If you have a regular labware with fewer than 96 wells that is still regularly-spaced and has little enough space between well
|
|
57
|
+
walls that it's reasonable to use with multiple channels, you probably want wells_covered_trough.
|
|
58
|
+
"""
|
|
59
|
+
target_row_index, target_column_index = row_col_ordinals_from_column_major_map(
|
|
60
|
+
target_well, target_wells_by_column
|
|
61
|
+
)
|
|
62
|
+
column_downsample = len(target_wells_by_column) // 12
|
|
63
|
+
row_downsample = len(target_wells_by_column[0]) // 8
|
|
64
|
+
if column_downsample < 1 or row_downsample < 1:
|
|
65
|
+
raise InvalidStoredData(
|
|
66
|
+
"This labware cannot be used wells_covered_dense because it is less dense than an SBS 96 standard"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
for nozzle_column in range(len(nozzle_map.columns)):
|
|
70
|
+
target_column_offset = nozzle_column * column_downsample
|
|
71
|
+
for nozzle_row in range(len(nozzle_map.rows)):
|
|
72
|
+
target_row_offset = nozzle_row * row_downsample
|
|
73
|
+
if nozzle_map.starting_nozzle == "A1":
|
|
74
|
+
if (
|
|
75
|
+
target_column_index + target_column_offset
|
|
76
|
+
< len(target_wells_by_column)
|
|
77
|
+
) and (
|
|
78
|
+
target_row_index + target_row_offset
|
|
79
|
+
< len(target_wells_by_column[target_column_index])
|
|
80
|
+
):
|
|
81
|
+
yield target_wells_by_column[
|
|
82
|
+
target_column_index + target_column_offset
|
|
83
|
+
][target_row_index + target_row_offset]
|
|
84
|
+
elif nozzle_map.starting_nozzle == "A12":
|
|
85
|
+
if (target_column_index - target_column_offset >= 0) and (
|
|
86
|
+
target_row_index + target_row_offset
|
|
87
|
+
< len(target_wells_by_column[target_column_index])
|
|
88
|
+
):
|
|
89
|
+
yield target_wells_by_column[
|
|
90
|
+
target_column_index - target_column_offset
|
|
91
|
+
][target_row_index + target_row_offset]
|
|
92
|
+
elif nozzle_map.starting_nozzle == "H1":
|
|
93
|
+
if (
|
|
94
|
+
target_column_index + target_column_offset
|
|
95
|
+
< len(target_wells_by_column)
|
|
96
|
+
) and (target_row_index - target_row_offset >= 0):
|
|
97
|
+
yield target_wells_by_column[
|
|
98
|
+
target_column_index + target_column_offset
|
|
99
|
+
][target_row_index - target_row_offset]
|
|
100
|
+
elif nozzle_map.starting_nozzle == "H12":
|
|
101
|
+
if (target_column_index - target_column_offset >= 0) and (
|
|
102
|
+
target_row_index - target_row_offset >= 0
|
|
103
|
+
):
|
|
104
|
+
yield target_wells_by_column[
|
|
105
|
+
target_column_index - target_column_offset
|
|
106
|
+
][target_row_index - target_row_offset]
|
|
107
|
+
else:
|
|
108
|
+
raise InvalidProtocolData(
|
|
109
|
+
f"A pipette nozzle configuration may not having a starting nozzle of {nozzle_map.starting_nozzle}"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def wells_covered_sparse( # noqa: C901
|
|
114
|
+
nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]]
|
|
115
|
+
) -> Iterator[str]:
|
|
116
|
+
"""Get the list of wells covered by a nozzle map on a column-oriented reservoir.
|
|
117
|
+
|
|
118
|
+
This function handles reservoirs whose wells span multiple rows and columns - the most common case is something like a
|
|
119
|
+
12-well reservoir, whose wells are the height of an SBS column and the width of an SBS row, or a 1-well reservoir whose well
|
|
120
|
+
is the size of an SBS active area.
|
|
121
|
+
"""
|
|
122
|
+
target_row_index, target_column_index = row_col_ordinals_from_column_major_map(
|
|
123
|
+
target_well, target_wells_by_column
|
|
124
|
+
)
|
|
125
|
+
column_upsample = 12 // len(target_wells_by_column)
|
|
126
|
+
row_upsample = 8 // len(target_wells_by_column[0])
|
|
127
|
+
if column_upsample < 1 or row_upsample < 1:
|
|
128
|
+
raise InvalidStoredData(
|
|
129
|
+
"This labware cannot be used with wells_covered_sparse because it is more dense than an SBS 96 standard."
|
|
130
|
+
)
|
|
131
|
+
for nozzle_column in range(max(1, len(nozzle_map.columns) // column_upsample)):
|
|
132
|
+
for nozzle_row in range(max(1, len(nozzle_map.rows) // row_upsample)):
|
|
133
|
+
if nozzle_map.starting_nozzle == "A1":
|
|
134
|
+
if (
|
|
135
|
+
target_column_index + nozzle_column < len(target_wells_by_column)
|
|
136
|
+
) and (
|
|
137
|
+
target_row_index + nozzle_row
|
|
138
|
+
< len(target_wells_by_column[target_column_index])
|
|
139
|
+
):
|
|
140
|
+
yield target_wells_by_column[target_column_index + nozzle_column][
|
|
141
|
+
target_row_index + nozzle_row
|
|
142
|
+
]
|
|
143
|
+
elif nozzle_map.starting_nozzle == "A12":
|
|
144
|
+
if (target_column_index - nozzle_column >= 0) and (
|
|
145
|
+
target_row_index + nozzle_row
|
|
146
|
+
< len(target_wells_by_column[target_column_index])
|
|
147
|
+
):
|
|
148
|
+
yield target_wells_by_column[target_column_index - nozzle_column][
|
|
149
|
+
target_row_index + nozzle_row
|
|
150
|
+
]
|
|
151
|
+
elif nozzle_map.starting_nozzle == "H1":
|
|
152
|
+
if (
|
|
153
|
+
target_column_index + nozzle_column
|
|
154
|
+
< len(target_wells_by_column[target_column_index])
|
|
155
|
+
) and (target_row_index - nozzle_row >= 0):
|
|
156
|
+
yield target_wells_by_column[target_column_index + nozzle_column][
|
|
157
|
+
target_row_index - nozzle_row
|
|
158
|
+
]
|
|
159
|
+
elif nozzle_map.starting_nozzle == "H12":
|
|
160
|
+
if (target_column_index - nozzle_column >= 0) and (
|
|
161
|
+
target_row_index - nozzle_row >= 0
|
|
162
|
+
):
|
|
163
|
+
yield target_wells_by_column[target_column_index - nozzle_column][
|
|
164
|
+
target_row_index - nozzle_row
|
|
165
|
+
]
|
|
166
|
+
else:
|
|
167
|
+
raise InvalidProtocolData(
|
|
168
|
+
f"A pipette nozzle configuration may not having a starting nozzle of {nozzle_map.starting_nozzle}"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def nozzles_per_well(
|
|
173
|
+
nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]]
|
|
174
|
+
) -> int:
|
|
175
|
+
"""Get the number of nozzles that will interact with each well in the labware.
|
|
176
|
+
|
|
177
|
+
For instance, if this is an SBS 96 or more dense, there is always 1 nozzle per well
|
|
178
|
+
that is interacted with (and some wells may not be interacted with at all). If this is
|
|
179
|
+
a 12-column reservoir, then all active nozzles in each column of the configuration will
|
|
180
|
+
interact with each well; so an 8-channel full config would have 8 nozzles per well,
|
|
181
|
+
and a 96 channel with a rectangle config from A1 to D12 would have 4 nozzles per well.
|
|
182
|
+
"""
|
|
183
|
+
_, target_column_index = row_col_ordinals_from_column_major_map(
|
|
184
|
+
target_well, target_wells_by_column
|
|
185
|
+
)
|
|
186
|
+
# labware as or more dense than a 96 plate will only ever have 1 nozzle per well (and some wells won't be touched)
|
|
187
|
+
if len(target_wells_by_column) >= len(nozzle_map.columns) and len(
|
|
188
|
+
target_wells_by_column[target_column_index]
|
|
189
|
+
) >= len(nozzle_map.rows):
|
|
190
|
+
return 1
|
|
191
|
+
return max(1, len(nozzle_map.columns) // len(target_wells_by_column)) * max(
|
|
192
|
+
1, len(nozzle_map.rows) // len(target_wells_by_column[target_column_index])
|
|
193
|
+
)
|