opentrons 8.2.0a4__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 +36 -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 +1 -1
- 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 +52 -70
- 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.0a4.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
- {opentrons-8.2.0a4.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Common functions between v1 transfer and liquid-class-based transfer."""
|
|
2
|
+
import enum
|
|
3
|
+
from typing import Iterable, Generator, Tuple, TypeVar, Literal
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TransferTipPolicyV2(enum.Enum):
|
|
7
|
+
ONCE = "once"
|
|
8
|
+
NEVER = "never"
|
|
9
|
+
ALWAYS = "always"
|
|
10
|
+
PER_SOURCE = "per source"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
TransferTipPolicyV2Type = Literal["once", "always", "per source", "never"]
|
|
14
|
+
|
|
15
|
+
Target = TypeVar("Target")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def check_valid_volume_parameters(
|
|
19
|
+
disposal_volume: float, air_gap: float, max_volume: float
|
|
20
|
+
) -> None:
|
|
21
|
+
if air_gap >= max_volume:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
"The air gap must be less than the maximum volume of the pipette"
|
|
24
|
+
)
|
|
25
|
+
elif disposal_volume >= max_volume:
|
|
26
|
+
raise ValueError(
|
|
27
|
+
"The disposal volume must be less than the maximum volume of the pipette"
|
|
28
|
+
)
|
|
29
|
+
elif disposal_volume + air_gap >= max_volume:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
"The sum of the air gap and disposal volume must be less than"
|
|
32
|
+
" the maximum volume of the pipette"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def expand_for_volume_constraints(
|
|
37
|
+
volumes: Iterable[float],
|
|
38
|
+
targets: Iterable[Target],
|
|
39
|
+
max_volume: float,
|
|
40
|
+
) -> Generator[Tuple[float, "Target"], None, None]:
|
|
41
|
+
"""Split a sequence of proposed transfers if necessary to keep each
|
|
42
|
+
transfer under the given max volume.
|
|
43
|
+
"""
|
|
44
|
+
# A final defense against an infinite loop.
|
|
45
|
+
# Raising a proper exception with a helpful message is left to calling code,
|
|
46
|
+
# because it has more context about what the user is trying to do.
|
|
47
|
+
assert max_volume > 0
|
|
48
|
+
for volume, target in zip(volumes, targets):
|
|
49
|
+
while volume > max_volume * 2:
|
|
50
|
+
yield max_volume, target
|
|
51
|
+
volume -= max_volume
|
|
52
|
+
|
|
53
|
+
if volume > max_volume:
|
|
54
|
+
volume /= 2
|
|
55
|
+
yield volume, target
|
|
56
|
+
yield volume, target
|
|
@@ -9,19 +9,18 @@ from typing import (
|
|
|
9
9
|
Callable,
|
|
10
10
|
Generator,
|
|
11
11
|
Iterator,
|
|
12
|
-
Iterable,
|
|
13
12
|
Sequence,
|
|
14
13
|
Tuple,
|
|
15
14
|
TypedDict,
|
|
16
15
|
TypeAlias,
|
|
17
16
|
TYPE_CHECKING,
|
|
18
|
-
TypeVar,
|
|
19
17
|
)
|
|
20
18
|
from opentrons.protocol_api.labware import Labware, Well
|
|
21
19
|
from opentrons import types
|
|
22
20
|
from opentrons.protocols.api_support.types import APIVersion
|
|
23
|
-
from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType
|
|
24
21
|
|
|
22
|
+
from . import common as tx_commons
|
|
23
|
+
from ..common import Mix, MixOpts, MixStrategy
|
|
25
24
|
|
|
26
25
|
AdvancedLiquidHandling = Union[
|
|
27
26
|
Well,
|
|
@@ -44,13 +43,6 @@ _PARTIAL_TIP_SUPPORT_ADDED = APIVersion(2, 18)
|
|
|
44
43
|
"""The version after which partial tip support and nozzle maps were made available."""
|
|
45
44
|
|
|
46
45
|
|
|
47
|
-
class MixStrategy(enum.Enum):
|
|
48
|
-
BOTH = enum.auto()
|
|
49
|
-
BEFORE = enum.auto()
|
|
50
|
-
AFTER = enum.auto()
|
|
51
|
-
NEVER = enum.auto()
|
|
52
|
-
|
|
53
|
-
|
|
54
46
|
class DropTipStrategy(enum.Enum):
|
|
55
47
|
TRASH = enum.auto()
|
|
56
48
|
RETURN = enum.auto()
|
|
@@ -239,34 +231,6 @@ PickUpTipOpts.presses.__doc__ = ":py:class:`int`"
|
|
|
239
231
|
PickUpTipOpts.increment.__doc__ = ":py:class:`int`"
|
|
240
232
|
|
|
241
233
|
|
|
242
|
-
class MixOpts(NamedTuple):
|
|
243
|
-
"""
|
|
244
|
-
Options to customize behavior of mix.
|
|
245
|
-
|
|
246
|
-
These options will be passed to
|
|
247
|
-
:py:meth:`InstrumentContext.mix` when it is called during the
|
|
248
|
-
transfer.
|
|
249
|
-
"""
|
|
250
|
-
|
|
251
|
-
repetitions: Optional[int] = None
|
|
252
|
-
volume: Optional[float] = None
|
|
253
|
-
rate: Optional[float] = None
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
MixOpts.repetitions.__doc__ = ":py:class:`int`"
|
|
257
|
-
MixOpts.volume.__doc__ = ":py:class:`float`"
|
|
258
|
-
MixOpts.rate.__doc__ = ":py:class:`float`"
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
class Mix(NamedTuple):
|
|
262
|
-
"""
|
|
263
|
-
Options to control mix behavior before aspirate and after dispense.
|
|
264
|
-
"""
|
|
265
|
-
|
|
266
|
-
mix_before: MixOpts = MixOpts()
|
|
267
|
-
mix_after: MixOpts = MixOpts()
|
|
268
|
-
|
|
269
|
-
|
|
270
234
|
Mix.mix_before.__doc__ = """
|
|
271
235
|
Options applied to mix before aspirate.
|
|
272
236
|
See :py:class:`.Mix.MixOpts`.
|
|
@@ -435,7 +399,7 @@ class TransferPlan:
|
|
|
435
399
|
# then avoid iterating through its Wells.
|
|
436
400
|
# ii. if using single channel pipettes, flatten a multi-dimensional
|
|
437
401
|
# list of Wells into a 1 dimensional list of Wells
|
|
438
|
-
pipette_configuration_type = NozzleConfigurationType.FULL
|
|
402
|
+
pipette_configuration_type = types.NozzleConfigurationType.FULL
|
|
439
403
|
normalized_sources: List[Union[Well, types.Location]]
|
|
440
404
|
normalized_dests: List[Union[Well, types.Location]]
|
|
441
405
|
if self._api_version >= _PARTIAL_TIP_SUPPORT_ADDED:
|
|
@@ -444,7 +408,7 @@ class TransferPlan:
|
|
|
444
408
|
)
|
|
445
409
|
if (
|
|
446
410
|
self._instr.channels > 1
|
|
447
|
-
and pipette_configuration_type == NozzleConfigurationType.FULL
|
|
411
|
+
and pipette_configuration_type == types.NozzleConfigurationType.FULL
|
|
448
412
|
):
|
|
449
413
|
normalized_sources, normalized_dests = self._multichannel_transfer(
|
|
450
414
|
srcs, dsts
|
|
@@ -534,12 +498,12 @@ class TransferPlan:
|
|
|
534
498
|
"""
|
|
535
499
|
# reform source target lists
|
|
536
500
|
sources, dests = self._extend_source_target_lists(self._sources, self._dests)
|
|
537
|
-
|
|
501
|
+
tx_commons.check_valid_volume_parameters(
|
|
538
502
|
disposal_volume=self._strategy.disposal_volume,
|
|
539
503
|
air_gap=self._strategy.air_gap,
|
|
540
504
|
max_volume=self._instr.max_volume,
|
|
541
505
|
)
|
|
542
|
-
plan_iter =
|
|
506
|
+
plan_iter = tx_commons.expand_for_volume_constraints(
|
|
543
507
|
self._volumes,
|
|
544
508
|
zip(sources, dests),
|
|
545
509
|
self._instr.max_volume
|
|
@@ -626,7 +590,7 @@ class TransferPlan:
|
|
|
626
590
|
|
|
627
591
|
"""
|
|
628
592
|
|
|
629
|
-
|
|
593
|
+
tx_commons.check_valid_volume_parameters(
|
|
630
594
|
disposal_volume=self._strategy.disposal_volume,
|
|
631
595
|
air_gap=self._strategy.air_gap,
|
|
632
596
|
max_volume=self._instr.max_volume,
|
|
@@ -637,7 +601,7 @@ class TransferPlan:
|
|
|
637
601
|
# recommend users to specify a disposal vol when using distribute.
|
|
638
602
|
# First method keeps distribute consistent with current behavior while
|
|
639
603
|
# the other maintains consistency in default behaviors of all functions
|
|
640
|
-
plan_iter =
|
|
604
|
+
plan_iter = tx_commons.expand_for_volume_constraints(
|
|
641
605
|
self._volumes,
|
|
642
606
|
self._dests,
|
|
643
607
|
# todo(mm, 2021-03-09): Is it right for this to be
|
|
@@ -686,29 +650,6 @@ class TransferPlan:
|
|
|
686
650
|
)
|
|
687
651
|
yield from self._new_tip_action()
|
|
688
652
|
|
|
689
|
-
Target = TypeVar("Target")
|
|
690
|
-
|
|
691
|
-
@staticmethod
|
|
692
|
-
def _expand_for_volume_constraints(
|
|
693
|
-
volumes: Iterable[float], targets: Iterable[Target], max_volume: float
|
|
694
|
-
) -> Generator[Tuple[float, "Target"], None, None]:
|
|
695
|
-
"""Split a sequence of proposed transfers if necessary to keep each
|
|
696
|
-
transfer under the given max volume.
|
|
697
|
-
"""
|
|
698
|
-
# A final defense against an infinite loop.
|
|
699
|
-
# Raising a proper exception with a helpful message is left to calling code,
|
|
700
|
-
# because it has more context about what the user is trying to do.
|
|
701
|
-
assert max_volume > 0
|
|
702
|
-
for volume, target in zip(volumes, targets):
|
|
703
|
-
while volume > max_volume * 2:
|
|
704
|
-
yield max_volume, target
|
|
705
|
-
volume -= max_volume
|
|
706
|
-
|
|
707
|
-
if volume > max_volume:
|
|
708
|
-
volume /= 2
|
|
709
|
-
yield volume, target
|
|
710
|
-
yield volume, target
|
|
711
|
-
|
|
712
653
|
def _plan_consolidate(self) -> Generator[TransferStep, None, None]:
|
|
713
654
|
"""
|
|
714
655
|
* **Source/ Dest:** Many sources to one destination
|
|
@@ -752,7 +693,7 @@ class TransferPlan:
|
|
|
752
693
|
# air_gap=self._strategy.air_gap,
|
|
753
694
|
# max_volume=self._instr.max_volume,
|
|
754
695
|
# )
|
|
755
|
-
plan_iter =
|
|
696
|
+
plan_iter = tx_commons.expand_for_volume_constraints(
|
|
756
697
|
# todo(mm, 2021-03-09): Is it right to use _instr.max_volume here?
|
|
757
698
|
# Why don't we account for tip max volume, disposal volume, or air
|
|
758
699
|
# gap?
|
|
@@ -929,8 +870,8 @@ class TransferPlan:
|
|
|
929
870
|
)
|
|
930
871
|
return volume
|
|
931
872
|
|
|
873
|
+
@staticmethod
|
|
932
874
|
def _create_volume_gradient(
|
|
933
|
-
self,
|
|
934
875
|
min_v: float,
|
|
935
876
|
max_v: float,
|
|
936
877
|
total: int,
|
|
@@ -947,22 +888,6 @@ class TransferPlan:
|
|
|
947
888
|
|
|
948
889
|
return [_map_volume(i) for i in range(total)]
|
|
949
890
|
|
|
950
|
-
def _check_valid_volume_parameters(
|
|
951
|
-
self, disposal_volume: float, air_gap: float, max_volume: float
|
|
952
|
-
) -> None:
|
|
953
|
-
if air_gap >= max_volume:
|
|
954
|
-
raise ValueError(
|
|
955
|
-
"The air gap must be less than the maximum volume of the pipette"
|
|
956
|
-
)
|
|
957
|
-
elif disposal_volume >= max_volume:
|
|
958
|
-
raise ValueError(
|
|
959
|
-
"The disposal volume must be less than the maximum volume of the pipette"
|
|
960
|
-
)
|
|
961
|
-
elif disposal_volume + air_gap >= max_volume:
|
|
962
|
-
raise ValueError(
|
|
963
|
-
"The sum of the air gap and disposal volume must be less than the maximum volume of the pipette"
|
|
964
|
-
)
|
|
965
|
-
|
|
966
891
|
def _check_valid_well_list(
|
|
967
892
|
self, well_list: List[Any], id: str, old_well_list: List[Any]
|
|
968
893
|
) -> None:
|
|
@@ -391,3 +391,13 @@ def requires_version(major: int, minor: int) -> Callable[[FuncT], FuncT]:
|
|
|
391
391
|
return cast(FuncT, _check_version_wrapper)
|
|
392
392
|
|
|
393
393
|
return _set_version
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class ModifiedList(list[str]):
|
|
397
|
+
def __contains__(self, item: object) -> bool:
|
|
398
|
+
if not isinstance(item, str):
|
|
399
|
+
return False
|
|
400
|
+
for name in self:
|
|
401
|
+
if name == item.replace("-", "_").lower():
|
|
402
|
+
return True
|
|
403
|
+
return False
|
opentrons/protocols/labware.py
CHANGED
|
@@ -2,13 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import json
|
|
5
|
-
|
|
5
|
+
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any, AnyStr, Dict, Optional, Union
|
|
7
|
+
from typing import Any, AnyStr, Dict, Optional, Union, List, Sequence, Literal
|
|
8
8
|
|
|
9
9
|
import jsonschema # type: ignore
|
|
10
10
|
|
|
11
11
|
from opentrons_shared_data import load_shared_data, get_shared_data_root
|
|
12
|
+
from opentrons.protocols.api_support.util import ModifiedList
|
|
12
13
|
from opentrons.protocols.api_support.constants import (
|
|
13
14
|
OPENTRONS_NAMESPACE,
|
|
14
15
|
CUSTOM_NAMESPACE,
|
|
@@ -16,10 +17,30 @@ from opentrons.protocols.api_support.constants import (
|
|
|
16
17
|
USER_DEFS_PATH,
|
|
17
18
|
)
|
|
18
19
|
from opentrons_shared_data.labware.types import LabwareDefinition
|
|
20
|
+
from opentrons_shared_data.errors.exceptions import InvalidProtocolData
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
MODULE_LOG = logging.getLogger(__name__)
|
|
22
24
|
|
|
25
|
+
LabwareProblem = Literal[
|
|
26
|
+
"no-schema-id", "bad-schema-id", "schema-mismatch", "invalid-json"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NotALabwareError(InvalidProtocolData):
|
|
31
|
+
def __init__(
|
|
32
|
+
self, problem: LabwareProblem, wrapping: Sequence[BaseException]
|
|
33
|
+
) -> None:
|
|
34
|
+
messages: dict[LabwareProblem, str] = {
|
|
35
|
+
"no-schema-id": "No schema ID present in file",
|
|
36
|
+
"bad-schema-id": "Bad schema ID in file",
|
|
37
|
+
"invalid-json": "File does not contain valid JSON",
|
|
38
|
+
"schema-mismatch": "File does not match labware schema",
|
|
39
|
+
}
|
|
40
|
+
super().__init__(
|
|
41
|
+
message=messages[problem], detail={"kind": problem}, wrapping=wrapping
|
|
42
|
+
)
|
|
43
|
+
|
|
23
44
|
|
|
24
45
|
def get_labware_definition(
|
|
25
46
|
load_name: str,
|
|
@@ -61,6 +82,29 @@ def get_labware_definition(
|
|
|
61
82
|
return _get_standard_labware_definition(load_name, namespace, version)
|
|
62
83
|
|
|
63
84
|
|
|
85
|
+
def get_all_labware_definitions(schema_version: str = "2") -> List[str]:
|
|
86
|
+
"""
|
|
87
|
+
Return a list of standard and custom labware definitions with load_name +
|
|
88
|
+
name_space + version existing on the robot
|
|
89
|
+
"""
|
|
90
|
+
labware_list = ModifiedList()
|
|
91
|
+
|
|
92
|
+
def _check_for_subdirectories(path: Union[str, Path, os.DirEntry[str]]) -> None:
|
|
93
|
+
with os.scandir(path) as top_path:
|
|
94
|
+
for sub_dir in top_path:
|
|
95
|
+
if sub_dir.is_dir():
|
|
96
|
+
labware_list.append(sub_dir.name)
|
|
97
|
+
|
|
98
|
+
# check for standard labware
|
|
99
|
+
_check_for_subdirectories(
|
|
100
|
+
get_shared_data_root() / STANDARD_DEFS_PATH / schema_version
|
|
101
|
+
)
|
|
102
|
+
# check for custom labware
|
|
103
|
+
for namespace in os.scandir(USER_DEFS_PATH):
|
|
104
|
+
_check_for_subdirectories(namespace)
|
|
105
|
+
return labware_list
|
|
106
|
+
|
|
107
|
+
|
|
64
108
|
def save_definition(
|
|
65
109
|
labware_def: LabwareDefinition, force: bool = False, location: Optional[Path] = None
|
|
66
110
|
) -> None:
|
|
@@ -102,7 +146,7 @@ def save_definition(
|
|
|
102
146
|
json.dump(labware_def, f)
|
|
103
147
|
|
|
104
148
|
|
|
105
|
-
def verify_definition(
|
|
149
|
+
def verify_definition( # noqa: C901
|
|
106
150
|
contents: Union[AnyStr, LabwareDefinition, Dict[str, Any]]
|
|
107
151
|
) -> LabwareDefinition:
|
|
108
152
|
"""Verify that an input string is a labware definition and return it.
|
|
@@ -114,14 +158,33 @@ def verify_definition(
|
|
|
114
158
|
:raises jsonschema.ValidationError: If the definition is not valid.
|
|
115
159
|
:returns: The parsed definition
|
|
116
160
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
161
|
+
schemata_by_version = {
|
|
162
|
+
2: json.loads(load_shared_data("labware/schemas/2.json").decode("utf-8")),
|
|
163
|
+
3: json.loads(load_shared_data("labware/schemas/3.json").decode("utf-8")),
|
|
164
|
+
}
|
|
119
165
|
|
|
120
166
|
if isinstance(contents, dict):
|
|
121
167
|
to_return = contents
|
|
122
168
|
else:
|
|
123
|
-
|
|
124
|
-
|
|
169
|
+
try:
|
|
170
|
+
to_return = json.loads(contents)
|
|
171
|
+
except json.JSONDecodeError as e:
|
|
172
|
+
raise NotALabwareError("invalid-json", [e]) from e
|
|
173
|
+
try:
|
|
174
|
+
schema_version = to_return["schemaVersion"]
|
|
175
|
+
except KeyError as e:
|
|
176
|
+
raise NotALabwareError("no-schema-id", [e]) from e
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
schema = schemata_by_version[schema_version]
|
|
180
|
+
except KeyError as e:
|
|
181
|
+
raise NotALabwareError("bad-schema-id", [e]) from e
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
jsonschema.validate(to_return, schema)
|
|
185
|
+
except jsonschema.ValidationError as e:
|
|
186
|
+
raise NotALabwareError("schema-mismatch", [e]) from e
|
|
187
|
+
|
|
125
188
|
# we can type ignore this because if it passes the jsonschema it has
|
|
126
189
|
# the correct structure
|
|
127
190
|
return to_return # type: ignore[return-value]
|
|
@@ -176,7 +239,6 @@ def _get_labware_definition_from_bundle(
|
|
|
176
239
|
def _get_standard_labware_definition(
|
|
177
240
|
load_name: str, namespace: Optional[str] = None, version: Optional[int] = None
|
|
178
241
|
) -> LabwareDefinition:
|
|
179
|
-
|
|
180
242
|
if version is None:
|
|
181
243
|
checked_version = 1
|
|
182
244
|
else:
|
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
from typing import Any, Dict, List, Optional, Union
|
|
11
11
|
|
|
12
|
-
from pydantic import
|
|
12
|
+
from pydantic import ConfigDict, BaseModel, Field
|
|
13
13
|
from typing_extensions import Literal
|
|
14
14
|
|
|
15
15
|
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
|
|
@@ -75,8 +75,7 @@ class Metadata(BaseModel):
|
|
|
75
75
|
Optional metadata about the protocol
|
|
76
76
|
"""
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
extra = Extra.allow
|
|
78
|
+
model_config = ConfigDict(extra="allow")
|
|
80
79
|
|
|
81
80
|
protocolName: Optional[str] = Field(
|
|
82
81
|
None, description="A short, human-readable name for the protocol"
|
|
@@ -574,8 +573,7 @@ class Pipettes(BaseModel):
|
|
|
574
573
|
Fields describing an individual pipette
|
|
575
574
|
"""
|
|
576
575
|
|
|
577
|
-
|
|
578
|
-
extra = Extra.allow
|
|
576
|
+
model_config = ConfigDict(extra="allow")
|
|
579
577
|
|
|
580
578
|
mount: Literal["left", "right"] = Field(
|
|
581
579
|
..., description="Where the pipette is mounted"
|
|
@@ -592,8 +590,7 @@ class Labware(BaseModel):
|
|
|
592
590
|
Fields describing a single labware on the deck
|
|
593
591
|
"""
|
|
594
592
|
|
|
595
|
-
|
|
596
|
-
extra = Extra.allow
|
|
593
|
+
model_config = ConfigDict(extra="allow")
|
|
597
594
|
|
|
598
595
|
slot: str = Field(
|
|
599
596
|
...,
|
|
@@ -616,8 +613,7 @@ class Modules(BaseModel):
|
|
|
616
613
|
Fields describing a single module on the deck
|
|
617
614
|
"""
|
|
618
615
|
|
|
619
|
-
|
|
620
|
-
extra = Extra.allow
|
|
616
|
+
model_config = ConfigDict(extra="allow")
|
|
621
617
|
|
|
622
618
|
slot: str = Field(
|
|
623
619
|
...,
|
opentrons/simulate.py
CHANGED
|
@@ -829,7 +829,9 @@ def _create_live_context_pe(
|
|
|
829
829
|
# Non-async would use call_soon_threadsafe(), which makes the waiting harder.
|
|
830
830
|
async def add_all_extra_labware() -> None:
|
|
831
831
|
for labware_definition_dict in extra_labware.values():
|
|
832
|
-
labware_definition = LabwareDefinition.
|
|
832
|
+
labware_definition = LabwareDefinition.model_validate(
|
|
833
|
+
labware_definition_dict
|
|
834
|
+
)
|
|
833
835
|
pe.add_labware_definition(labware_definition)
|
|
834
836
|
|
|
835
837
|
# Add extra_labware to ProtocolEngine, being careful not to modify ProtocolEngine from this
|
opentrons/types.py
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import enum
|
|
3
3
|
from math import sqrt, isclose
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
TYPE_CHECKING,
|
|
6
|
+
Any,
|
|
7
|
+
NamedTuple,
|
|
8
|
+
Iterator,
|
|
9
|
+
Union,
|
|
10
|
+
List,
|
|
11
|
+
Optional,
|
|
12
|
+
Protocol,
|
|
13
|
+
Dict,
|
|
14
|
+
)
|
|
5
15
|
|
|
6
16
|
from opentrons_shared_data.robot.types import RobotType
|
|
7
17
|
|
|
@@ -150,7 +160,7 @@ class Location:
|
|
|
150
160
|
point, labware = location
|
|
151
161
|
some_function_taking_both(*location)
|
|
152
162
|
"""
|
|
153
|
-
return iter((self._point, self._labware))
|
|
163
|
+
return iter((self._point, self._labware))
|
|
154
164
|
|
|
155
165
|
def __eq__(self, other: object) -> bool:
|
|
156
166
|
return (
|
|
@@ -253,6 +263,75 @@ class OT3MountType(str, enum.Enum):
|
|
|
253
263
|
GRIPPER = "gripper"
|
|
254
264
|
|
|
255
265
|
|
|
266
|
+
class AxisType(enum.Enum):
|
|
267
|
+
X = "X" # gantry
|
|
268
|
+
Y = "Y"
|
|
269
|
+
Z_L = "Z_L" # left pipette mount Z
|
|
270
|
+
Z_R = "Z_R" # right pipette mount Z
|
|
271
|
+
Z_G = "Z_G" # gripper mount Z
|
|
272
|
+
P_L = "P_L" # left pipette plunger
|
|
273
|
+
P_R = "P_R" # right pipette plunger
|
|
274
|
+
Q = "Q" # hi-throughput pipette tiprack grab
|
|
275
|
+
G = "G" # gripper grab
|
|
276
|
+
|
|
277
|
+
@classmethod
|
|
278
|
+
def axis_for_mount(cls, mount: Mount) -> "AxisType":
|
|
279
|
+
map_axis_to_mount = {
|
|
280
|
+
Mount.LEFT: cls.Z_L,
|
|
281
|
+
Mount.RIGHT: cls.Z_R,
|
|
282
|
+
Mount.EXTENSION: cls.Z_G,
|
|
283
|
+
}
|
|
284
|
+
return map_axis_to_mount[mount]
|
|
285
|
+
|
|
286
|
+
@classmethod
|
|
287
|
+
def mount_for_axis(cls, axis: "AxisType") -> Mount:
|
|
288
|
+
map_mount_to_axis = {
|
|
289
|
+
cls.Z_L: Mount.LEFT,
|
|
290
|
+
cls.Z_R: Mount.RIGHT,
|
|
291
|
+
cls.Z_G: Mount.EXTENSION,
|
|
292
|
+
}
|
|
293
|
+
return map_mount_to_axis[axis]
|
|
294
|
+
|
|
295
|
+
@classmethod
|
|
296
|
+
def plunger_axis_for_mount(cls, mount: Mount) -> "AxisType":
|
|
297
|
+
map_plunger_axis_mount = {Mount.LEFT: cls.P_L, Mount.RIGHT: cls.P_R}
|
|
298
|
+
return map_plunger_axis_mount[mount]
|
|
299
|
+
|
|
300
|
+
@classmethod
|
|
301
|
+
def ot2_axes(cls) -> List["AxisType"]:
|
|
302
|
+
return [
|
|
303
|
+
AxisType.X,
|
|
304
|
+
AxisType.Y,
|
|
305
|
+
AxisType.Z_L,
|
|
306
|
+
AxisType.Z_R,
|
|
307
|
+
AxisType.P_L,
|
|
308
|
+
AxisType.P_R,
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
@classmethod
|
|
312
|
+
def flex_gantry_axes(cls) -> List["AxisType"]:
|
|
313
|
+
return [
|
|
314
|
+
AxisType.X,
|
|
315
|
+
AxisType.Y,
|
|
316
|
+
AxisType.Z_L,
|
|
317
|
+
AxisType.Z_R,
|
|
318
|
+
AxisType.Z_G,
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
@classmethod
|
|
322
|
+
def ot2_gantry_axes(cls) -> List["AxisType"]:
|
|
323
|
+
return [
|
|
324
|
+
AxisType.X,
|
|
325
|
+
AxisType.Y,
|
|
326
|
+
AxisType.Z_L,
|
|
327
|
+
AxisType.Z_R,
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
AxisMapType = Dict[AxisType, float]
|
|
332
|
+
StringAxisMap = Dict[str, float]
|
|
333
|
+
|
|
334
|
+
|
|
256
335
|
# TODO(mc, 2020-11-09): this makes sense in shared-data or other common
|
|
257
336
|
# model library
|
|
258
337
|
# https://github.com/Opentrons/opentrons/pull/6943#discussion_r519029833
|
|
@@ -426,3 +505,84 @@ class TransferTipPolicy(enum.Enum):
|
|
|
426
505
|
|
|
427
506
|
DeckLocation = Union[int, str]
|
|
428
507
|
ALLOWED_PRIMARY_NOZZLES = ["A1", "H1", "A12", "H12"]
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class NozzleConfigurationType(enum.Enum):
|
|
511
|
+
"""Short names for types of nozzle configurations.
|
|
512
|
+
|
|
513
|
+
Represents the current nozzle configuration stored in a NozzleMap.
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
COLUMN = "COLUMN"
|
|
517
|
+
ROW = "ROW"
|
|
518
|
+
SINGLE = "SINGLE"
|
|
519
|
+
FULL = "FULL"
|
|
520
|
+
SUBRECT = "SUBRECT"
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
class NozzleMapInterface(Protocol):
|
|
524
|
+
"""
|
|
525
|
+
A NozzleMap instance represents a specific configuration of active nozzles on a pipette.
|
|
526
|
+
|
|
527
|
+
It exposes properties of the configuration like the configuration's front-right, front-left,
|
|
528
|
+
back-left and starting nozzles as well as a map of all the nozzles active in the configuration.
|
|
529
|
+
|
|
530
|
+
Because NozzleMaps represent configurations directly, the properties of the NozzleMap may not
|
|
531
|
+
match the properties of the physical pipette. For instance, a NozzleMap for a single channel
|
|
532
|
+
configuration of an 8-channel pipette - say, A1 only - will have its front left, front right,
|
|
533
|
+
and active channels all be A1, while the physical configuration would have the front right
|
|
534
|
+
channel be H1.
|
|
535
|
+
"""
|
|
536
|
+
|
|
537
|
+
@property
|
|
538
|
+
def starting_nozzle(self) -> str:
|
|
539
|
+
"""The nozzle that automated operations that count nozzles should start at."""
|
|
540
|
+
...
|
|
541
|
+
|
|
542
|
+
@property
|
|
543
|
+
def rows(self) -> dict[str, list[str]]:
|
|
544
|
+
"""A map of all the rows active in this configuration."""
|
|
545
|
+
...
|
|
546
|
+
|
|
547
|
+
@property
|
|
548
|
+
def columns(self) -> dict[str, list[str]]:
|
|
549
|
+
"""A map of all the columns active in this configuration."""
|
|
550
|
+
...
|
|
551
|
+
|
|
552
|
+
@property
|
|
553
|
+
def back_left(self) -> str:
|
|
554
|
+
"""The backest, leftest (i.e. back if it's a column, left if it's a row) nozzle of the configuration.
|
|
555
|
+
|
|
556
|
+
Note: This is the value relevant for this particular configuration, and it may not represent the back left nozzle
|
|
557
|
+
of the underlying physical pipette. For instance, the back-left nozzle of a configuration representing nozzles
|
|
558
|
+
D7 to H12 of a 96-channel pipette is D7, which is not the back-left nozzle of the physical pipette (A1).
|
|
559
|
+
"""
|
|
560
|
+
...
|
|
561
|
+
|
|
562
|
+
@property
|
|
563
|
+
def configuration(self) -> NozzleConfigurationType:
|
|
564
|
+
"""The kind of configuration represented by this nozzle map."""
|
|
565
|
+
...
|
|
566
|
+
|
|
567
|
+
@property
|
|
568
|
+
def front_right(self) -> str:
|
|
569
|
+
"""The frontest, rightest (i.e. front if it's a column, right if it's a row) nozzle of the configuration.
|
|
570
|
+
|
|
571
|
+
Note: This is the value relevant for this configuration, not the physical pipette. See the note on back_left.
|
|
572
|
+
"""
|
|
573
|
+
...
|
|
574
|
+
|
|
575
|
+
@property
|
|
576
|
+
def tip_count(self) -> int:
|
|
577
|
+
"""The total number of active nozzles in the configuration, and thus the number of tips that will be picked up."""
|
|
578
|
+
...
|
|
579
|
+
|
|
580
|
+
@property
|
|
581
|
+
def physical_nozzle_count(self) -> int:
|
|
582
|
+
"""The number of actual physical nozzles on the pipette, regardless of configuration."""
|
|
583
|
+
...
|
|
584
|
+
|
|
585
|
+
@property
|
|
586
|
+
def active_nozzles(self) -> list[str]:
|
|
587
|
+
"""An unstructured list of all nozzles active in the configuration."""
|
|
588
|
+
...
|
|
@@ -6,7 +6,6 @@ import contextlib
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
|
-
from json import JSONDecodeError
|
|
10
9
|
import pathlib
|
|
11
10
|
import subprocess
|
|
12
11
|
import sys
|
|
@@ -21,8 +20,6 @@ from typing import (
|
|
|
21
20
|
TYPE_CHECKING,
|
|
22
21
|
)
|
|
23
22
|
|
|
24
|
-
from jsonschema import ValidationError # type: ignore
|
|
25
|
-
|
|
26
23
|
from opentrons.calibration_storage.deck_configuration import (
|
|
27
24
|
deserialize_deck_configuration,
|
|
28
25
|
)
|
|
@@ -32,7 +29,7 @@ from opentrons.config import (
|
|
|
32
29
|
JUPYTER_NOTEBOOK_LABWARE_DIR,
|
|
33
30
|
SystemArchitecture,
|
|
34
31
|
)
|
|
35
|
-
from opentrons.
|
|
32
|
+
from opentrons.protocols import labware
|
|
36
33
|
from opentrons.calibration_storage import helpers
|
|
37
34
|
from opentrons.protocol_engine.errors.error_occurrence import (
|
|
38
35
|
ErrorOccurrence as ProtocolEngineErrorOccurrence,
|
|
@@ -79,7 +76,7 @@ def labware_from_paths(
|
|
|
79
76
|
if child.is_file() and child.suffix.endswith("json"):
|
|
80
77
|
try:
|
|
81
78
|
defn = labware.verify_definition(child.read_bytes())
|
|
82
|
-
except
|
|
79
|
+
except labware.NotALabwareError:
|
|
83
80
|
log.info(f"{child}: invalid labware, ignoring")
|
|
84
81
|
log.debug(
|
|
85
82
|
f"{child}: labware invalid because of this exception.",
|