opentrons 8.3.0a0__py2.py3-none-any.whl → 8.3.0a2__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/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 +28 -20
- opentrons/hardware_control/backends/flex_protocol.py +4 -6
- opentrons/hardware_control/backends/ot3controller.py +177 -59
- opentrons/hardware_control/backends/ot3simulator.py +10 -8
- opentrons/hardware_control/backends/ot3utils.py +3 -13
- opentrons/hardware_control/dev_types.py +2 -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 +9 -21
- 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 +13 -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 +34 -22
- 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 +18 -0
- opentrons/hardware_control/protocols/motion_controller.py +6 -0
- opentrons/hardware_control/robot_calibration.py +1 -1
- opentrons/hardware_control/types.py +61 -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 +67 -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 +42 -4
- opentrons/protocol_api/core/labware.py +13 -4
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +34 -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 +34 -3
- opentrons/protocol_api/core/protocol.py +34 -1
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/instrument_context.py +145 -43
- opentrons/protocol_api/labware.py +231 -7
- 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 +261 -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 +81 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +0 -2
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +19 -5
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +0 -1
- opentrons/protocol_engine/commands/absorbance_reader/read.py +32 -9
- 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 +101 -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 +13 -3
- 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 +60 -25
- opentrons/protocol_engine/commands/load_labware.py +29 -7
- 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 +19 -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 +8 -2
- 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 +4 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +12 -3
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +1 -4
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +1 -4
- 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 +8 -0
- opentrons/protocol_engine/errors/error_occurrence.py +19 -20
- opentrons/protocol_engine/errors/exceptions.py +50 -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 +364 -8
- opentrons/protocol_engine/execution/movement.py +27 -0
- opentrons/protocol_engine/execution/pipetting.py +5 -1
- opentrons/protocol_engine/execution/tip_handler.py +4 -6
- 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 +5 -0
- opentrons/protocol_engine/resources/module_data_provider.py +1 -1
- opentrons/protocol_engine/resources/pipette_data_provider.py +12 -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 +17 -13
- opentrons/protocol_engine/state/files.py +10 -12
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/frustum_helpers.py +57 -32
- opentrons/protocol_engine/state/geometry.py +47 -1
- opentrons/protocol_engine/state/labware.py +79 -25
- 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 +135 -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.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/METADATA +16 -15
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/RECORD +229 -202
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/WHEEL +1 -1
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/LICENSE +0 -0
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/entry_points.txt +0 -0
- {opentrons-8.3.0a0.dist-info → opentrons-8.3.0a2.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
|
@@ -32,6 +32,11 @@ def validate_definition_is_lid(definition: LabwareDefinition) -> bool:
|
|
|
32
32
|
return LabwareRole.lid in definition.allowedRoles
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def validate_definition_is_system(definition: LabwareDefinition) -> bool:
|
|
36
|
+
"""Validate that one of the definition's allowed roles is `system`."""
|
|
37
|
+
return LabwareRole.system in definition.allowedRoles
|
|
38
|
+
|
|
39
|
+
|
|
35
40
|
def validate_labware_can_be_stacked(
|
|
36
41
|
top_labware_definition: LabwareDefinition, below_labware_load_name: str
|
|
37
42
|
) -> bool:
|
|
@@ -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,8 @@ 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
|
|
70
72
|
available_sensors: pipette_definition.AvailableSensorDefinition
|
|
71
73
|
|
|
72
74
|
|
|
@@ -258,6 +260,7 @@ class VirtualPipetteDataProvider:
|
|
|
258
260
|
|
|
259
261
|
pip_back_left = config.pipette_bounding_box_offsets.back_left_corner
|
|
260
262
|
pip_front_right = config.pipette_bounding_box_offsets.front_right_corner
|
|
263
|
+
plunger_positions = config.plunger_positions_configurations[liquid_class]
|
|
261
264
|
return LoadedStaticPipetteData(
|
|
262
265
|
model=str(pipette_model),
|
|
263
266
|
display_name=config.display_name,
|
|
@@ -286,6 +289,13 @@ class VirtualPipetteDataProvider:
|
|
|
286
289
|
pip_front_right[0], pip_front_right[1], pip_front_right[2]
|
|
287
290
|
),
|
|
288
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,
|
|
289
299
|
available_sensors=config.available_sensors
|
|
290
300
|
or pipette_definition.AvailableSensorDefinition(sensors=[]),
|
|
291
301
|
)
|
|
@@ -340,6 +350,8 @@ def get_pipette_static_config(
|
|
|
340
350
|
front_right_offset[0], front_right_offset[1], front_right_offset[2]
|
|
341
351
|
),
|
|
342
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"],
|
|
343
355
|
available_sensors=available_sensors,
|
|
344
356
|
)
|
|
345
357
|
|
|
@@ -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
|
+
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Basic addressable area data state and store."""
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from functools import cached_property
|
|
4
|
-
from typing import Dict, List, Optional, Set
|
|
4
|
+
from typing import Dict, List, Optional, Set
|
|
5
5
|
|
|
6
6
|
from opentrons_shared_data.robot.types import RobotType, RobotDefinition
|
|
7
7
|
from opentrons_shared_data.deck.types import (
|
|
@@ -12,14 +12,6 @@ from opentrons_shared_data.deck.types import (
|
|
|
12
12
|
|
|
13
13
|
from opentrons.types import Point, DeckSlotName
|
|
14
14
|
|
|
15
|
-
from ..commands import (
|
|
16
|
-
Command,
|
|
17
|
-
LoadLabwareResult,
|
|
18
|
-
LoadModuleResult,
|
|
19
|
-
MoveLabwareResult,
|
|
20
|
-
MoveToAddressableAreaResult,
|
|
21
|
-
MoveToAddressableAreaForDropTipResult,
|
|
22
|
-
)
|
|
23
15
|
from ..errors import (
|
|
24
16
|
IncompatibleAddressableAreaError,
|
|
25
17
|
AreaNotInDeckConfigurationError,
|
|
@@ -29,19 +21,18 @@ from ..errors import (
|
|
|
29
21
|
)
|
|
30
22
|
from ..resources import deck_configuration_provider
|
|
31
23
|
from ..types import (
|
|
32
|
-
DeckSlotLocation,
|
|
33
|
-
AddressableAreaLocation,
|
|
34
24
|
AddressableArea,
|
|
35
25
|
PotentialCutoutFixture,
|
|
36
26
|
DeckConfigurationType,
|
|
37
27
|
Dimensions,
|
|
38
28
|
)
|
|
29
|
+
from ..actions.get_state_update import get_state_updates
|
|
39
30
|
from ..actions import (
|
|
40
31
|
Action,
|
|
41
|
-
SucceedCommandAction,
|
|
42
32
|
SetDeckConfigurationAction,
|
|
43
33
|
AddAddressableAreaAction,
|
|
44
34
|
)
|
|
35
|
+
from . import update_types
|
|
45
36
|
from .config import Config
|
|
46
37
|
from ._abstract_store import HasState, HandlesActions
|
|
47
38
|
|
|
@@ -193,10 +184,14 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
193
184
|
|
|
194
185
|
def handle_action(self, action: Action) -> None:
|
|
195
186
|
"""Modify state in reaction to an action."""
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
187
|
+
for state_update in get_state_updates(action):
|
|
188
|
+
if state_update.addressable_area_used != update_types.NO_CHANGE:
|
|
189
|
+
self._add_addressable_area(
|
|
190
|
+
state_update.addressable_area_used.addressable_area_name
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if isinstance(action, AddAddressableAreaAction):
|
|
194
|
+
self._add_addressable_area(action.addressable_area_name)
|
|
200
195
|
elif isinstance(action, SetDeckConfigurationAction):
|
|
201
196
|
current_state = self._state
|
|
202
197
|
if (
|
|
@@ -211,28 +206,6 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
211
206
|
)
|
|
212
207
|
)
|
|
213
208
|
|
|
214
|
-
def _handle_command(self, command: Command) -> None:
|
|
215
|
-
"""Modify state in reaction to a command."""
|
|
216
|
-
if isinstance(command.result, LoadLabwareResult):
|
|
217
|
-
location = command.params.location
|
|
218
|
-
if isinstance(location, (DeckSlotLocation, AddressableAreaLocation)):
|
|
219
|
-
self._check_location_is_addressable_area(location)
|
|
220
|
-
|
|
221
|
-
elif isinstance(command.result, MoveLabwareResult):
|
|
222
|
-
location = command.params.newLocation
|
|
223
|
-
if isinstance(location, (DeckSlotLocation, AddressableAreaLocation)):
|
|
224
|
-
self._check_location_is_addressable_area(location)
|
|
225
|
-
|
|
226
|
-
elif isinstance(command.result, LoadModuleResult):
|
|
227
|
-
self._check_location_is_addressable_area(command.params.location)
|
|
228
|
-
|
|
229
|
-
elif isinstance(
|
|
230
|
-
command.result,
|
|
231
|
-
(MoveToAddressableAreaResult, MoveToAddressableAreaForDropTipResult),
|
|
232
|
-
):
|
|
233
|
-
addressable_area_name = command.params.addressableAreaName
|
|
234
|
-
self._check_location_is_addressable_area(addressable_area_name)
|
|
235
|
-
|
|
236
209
|
@staticmethod
|
|
237
210
|
def _get_addressable_areas_from_deck_configuration(
|
|
238
211
|
deck_config: DeckConfigurationType, deck_definition: DeckDefinitionV5
|
|
@@ -260,16 +233,7 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
260
233
|
)
|
|
261
234
|
return {area.area_name: area for area in addressable_areas}
|
|
262
235
|
|
|
263
|
-
def
|
|
264
|
-
self, location: Union[DeckSlotLocation, AddressableAreaLocation, str]
|
|
265
|
-
) -> None:
|
|
266
|
-
if isinstance(location, DeckSlotLocation):
|
|
267
|
-
addressable_area_name = location.slotName.id
|
|
268
|
-
elif isinstance(location, AddressableAreaLocation):
|
|
269
|
-
addressable_area_name = location.addressableAreaName
|
|
270
|
-
else:
|
|
271
|
-
addressable_area_name = location
|
|
272
|
-
|
|
236
|
+
def _add_addressable_area(self, addressable_area_name: str) -> None:
|
|
273
237
|
if addressable_area_name not in self._state.loaded_addressable_areas_by_name:
|
|
274
238
|
cutout_id = self._validate_addressable_area_for_simulation(
|
|
275
239
|
addressable_area_name
|
|
@@ -323,7 +287,7 @@ class AddressableAreaStore(HasState[AddressableAreaState], HandlesActions):
|
|
|
323
287
|
return cutout_id
|
|
324
288
|
|
|
325
289
|
|
|
326
|
-
class AddressableAreaView
|
|
290
|
+
class AddressableAreaView:
|
|
327
291
|
"""Read-only addressable area state view."""
|
|
328
292
|
|
|
329
293
|
_state: AddressableAreaState
|
|
@@ -345,8 +309,8 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
345
309
|
@cached_property
|
|
346
310
|
def mount_offsets(self) -> Dict[str, Point]:
|
|
347
311
|
"""The left and right mount offsets of the robot."""
|
|
348
|
-
left_offset = self.
|
|
349
|
-
right_offset = self.
|
|
312
|
+
left_offset = self._state.robot_definition["mountOffsets"]["left"]
|
|
313
|
+
right_offset = self._state.robot_definition["mountOffsets"]["right"]
|
|
350
314
|
return {
|
|
351
315
|
"left": Point(x=left_offset[0], y=left_offset[1], z=left_offset[2]),
|
|
352
316
|
"right": Point(x=right_offset[0], y=right_offset[1], z=right_offset[2]),
|
|
@@ -355,10 +319,10 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
355
319
|
@cached_property
|
|
356
320
|
def padding_offsets(self) -> Dict[str, float]:
|
|
357
321
|
"""The padding offsets to be applied to the deck extents of the robot."""
|
|
358
|
-
rear_offset = self.
|
|
359
|
-
front_offset = self.
|
|
360
|
-
left_side_offset = self.
|
|
361
|
-
right_side_offset = self.
|
|
322
|
+
rear_offset = self._state.robot_definition["paddingOffsets"]["rear"]
|
|
323
|
+
front_offset = self._state.robot_definition["paddingOffsets"]["front"]
|
|
324
|
+
left_side_offset = self._state.robot_definition["paddingOffsets"]["leftSide"]
|
|
325
|
+
right_side_offset = self._state.robot_definition["paddingOffsets"]["rightSide"]
|
|
362
326
|
return {
|
|
363
327
|
"rear": rear_offset,
|
|
364
328
|
"front": front_offset,
|
|
@@ -420,12 +384,12 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
420
384
|
_get_conflicting_addressable_areas_error_string(
|
|
421
385
|
self._state.potential_cutout_fixtures_by_cutout_id[cutout_id],
|
|
422
386
|
self._state.loaded_addressable_areas_by_name,
|
|
423
|
-
self.
|
|
387
|
+
self._state.deck_definition,
|
|
424
388
|
)
|
|
425
389
|
)
|
|
426
390
|
area_display_name = (
|
|
427
391
|
deck_configuration_provider.get_addressable_area_display_name(
|
|
428
|
-
area_name, self.
|
|
392
|
+
area_name, self._state.deck_definition
|
|
429
393
|
)
|
|
430
394
|
)
|
|
431
395
|
raise IncompatibleAddressableAreaError(
|
|
@@ -504,7 +468,7 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
504
468
|
addressable_area_name: str,
|
|
505
469
|
) -> Point:
|
|
506
470
|
"""Get the offset form cutout fixture of an addressable area."""
|
|
507
|
-
for addressable_area in self.
|
|
471
|
+
for addressable_area in self._state.deck_definition["locations"][
|
|
508
472
|
"addressableAreas"
|
|
509
473
|
]:
|
|
510
474
|
if addressable_area["id"] == addressable_area_name:
|
|
@@ -568,7 +532,7 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
568
532
|
self, slot_name: DeckSlotName
|
|
569
533
|
) -> Optional[CutoutFixture]:
|
|
570
534
|
"""Get the Cutout Fixture currently loaded where a specific Deck Slot would be."""
|
|
571
|
-
deck_config = self.
|
|
535
|
+
deck_config = self._state.deck_configuration
|
|
572
536
|
if deck_config:
|
|
573
537
|
slot_cutout_id = DECK_SLOT_TO_CUTOUT_MAP[slot_name]
|
|
574
538
|
slot_cutout_fixture = None
|
|
@@ -581,7 +545,7 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
581
545
|
if cutout_id == slot_cutout_id:
|
|
582
546
|
slot_cutout_fixture = (
|
|
583
547
|
deck_configuration_provider.get_cutout_fixture(
|
|
584
|
-
cutout_fixture_id, self.
|
|
548
|
+
cutout_fixture_id, self._state.deck_definition
|
|
585
549
|
)
|
|
586
550
|
)
|
|
587
551
|
return slot_cutout_fixture
|
|
@@ -605,7 +569,7 @@ class AddressableAreaView(HasState[AddressableAreaState]):
|
|
|
605
569
|
self, slot_name: DeckSlotName
|
|
606
570
|
) -> Optional[str]:
|
|
607
571
|
"""Get the serial number provided by the deck configuration for a Fixture at a given location."""
|
|
608
|
-
deck_config = self.
|
|
572
|
+
deck_config = self._state.deck_configuration
|
|
609
573
|
if deck_config:
|
|
610
574
|
slot_cutout_id = DECK_SLOT_TO_CUTOUT_MAP[slot_name]
|
|
611
575
|
# This will only ever be one under current assumptions
|
|
@@ -24,6 +24,9 @@ class CommandHistory:
|
|
|
24
24
|
_all_command_ids: List[str]
|
|
25
25
|
"""All command IDs, in insertion order."""
|
|
26
26
|
|
|
27
|
+
_all_failed_command_ids: List[str]
|
|
28
|
+
"""All failed command IDs, in insertion order."""
|
|
29
|
+
|
|
27
30
|
_all_command_ids_but_fixit_command_ids: List[str]
|
|
28
31
|
"""All command IDs besides fixit command intents, in insertion order."""
|
|
29
32
|
|
|
@@ -47,6 +50,7 @@ class CommandHistory:
|
|
|
47
50
|
|
|
48
51
|
def __init__(self) -> None:
|
|
49
52
|
self._all_command_ids = []
|
|
53
|
+
self._all_failed_command_ids = []
|
|
50
54
|
self._all_command_ids_but_fixit_command_ids = []
|
|
51
55
|
self._queued_command_ids = OrderedSet()
|
|
52
56
|
self._queued_setup_command_ids = OrderedSet()
|
|
@@ -101,6 +105,13 @@ class CommandHistory:
|
|
|
101
105
|
for command_id in self._all_command_ids
|
|
102
106
|
]
|
|
103
107
|
|
|
108
|
+
def get_all_failed_commands(self) -> List[Command]:
|
|
109
|
+
"""Get all failed commands."""
|
|
110
|
+
return [
|
|
111
|
+
self._commands_by_id[command_id].command
|
|
112
|
+
for command_id in self._all_failed_command_ids
|
|
113
|
+
]
|
|
114
|
+
|
|
104
115
|
def get_filtered_command_ids(self, include_fixit_commands: bool) -> List[str]:
|
|
105
116
|
"""Get all fixit command IDs."""
|
|
106
117
|
if include_fixit_commands:
|
|
@@ -242,6 +253,7 @@ class CommandHistory:
|
|
|
242
253
|
self._remove_queue_id(command.id)
|
|
243
254
|
self._remove_setup_queue_id(command.id)
|
|
244
255
|
self._set_most_recently_completed_command_id(command.id)
|
|
256
|
+
self._all_failed_command_ids.append(command.id)
|
|
245
257
|
|
|
246
258
|
def _add(self, command_id: str, command_entry: CommandEntry) -> None:
|
|
247
259
|
"""Create or update a command entry."""
|