opentrons 8.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/__init__.py +150 -0
- opentrons/_version.py +34 -0
- opentrons/calibration_storage/__init__.py +54 -0
- opentrons/calibration_storage/deck_configuration.py +62 -0
- opentrons/calibration_storage/encoder_decoder.py +31 -0
- opentrons/calibration_storage/file_operators.py +142 -0
- opentrons/calibration_storage/helpers.py +103 -0
- opentrons/calibration_storage/ot2/__init__.py +34 -0
- opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
- opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
- opentrons/calibration_storage/ot2/models/__init__.py +0 -0
- opentrons/calibration_storage/ot2/models/v1.py +149 -0
- opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
- opentrons/calibration_storage/ot2/tip_length.py +281 -0
- opentrons/calibration_storage/ot3/__init__.py +31 -0
- opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
- opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
- opentrons/calibration_storage/ot3/models/__init__.py +0 -0
- opentrons/calibration_storage/ot3/models/v1.py +122 -0
- opentrons/calibration_storage/ot3/module_offset.py +138 -0
- opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
- opentrons/calibration_storage/types.py +45 -0
- opentrons/cli/__init__.py +21 -0
- opentrons/cli/__main__.py +5 -0
- opentrons/cli/analyze.py +557 -0
- opentrons/config/__init__.py +631 -0
- opentrons/config/advanced_settings.py +871 -0
- opentrons/config/defaults_ot2.py +214 -0
- opentrons/config/defaults_ot3.py +499 -0
- opentrons/config/feature_flags.py +86 -0
- opentrons/config/gripper_config.py +55 -0
- opentrons/config/reset.py +203 -0
- opentrons/config/robot_configs.py +187 -0
- opentrons/config/types.py +183 -0
- opentrons/drivers/__init__.py +0 -0
- opentrons/drivers/absorbance_reader/__init__.py +11 -0
- opentrons/drivers/absorbance_reader/abstract.py +72 -0
- opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
- opentrons/drivers/absorbance_reader/driver.py +81 -0
- opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
- opentrons/drivers/absorbance_reader/simulator.py +84 -0
- opentrons/drivers/asyncio/__init__.py +0 -0
- opentrons/drivers/asyncio/communication/__init__.py +22 -0
- opentrons/drivers/asyncio/communication/async_serial.py +187 -0
- opentrons/drivers/asyncio/communication/errors.py +88 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +557 -0
- opentrons/drivers/command_builder.py +102 -0
- opentrons/drivers/flex_stacker/__init__.py +13 -0
- opentrons/drivers/flex_stacker/abstract.py +214 -0
- opentrons/drivers/flex_stacker/driver.py +768 -0
- opentrons/drivers/flex_stacker/errors.py +68 -0
- opentrons/drivers/flex_stacker/simulator.py +309 -0
- opentrons/drivers/flex_stacker/types.py +367 -0
- opentrons/drivers/flex_stacker/utils.py +19 -0
- opentrons/drivers/heater_shaker/__init__.py +5 -0
- opentrons/drivers/heater_shaker/abstract.py +76 -0
- opentrons/drivers/heater_shaker/driver.py +204 -0
- opentrons/drivers/heater_shaker/simulator.py +94 -0
- opentrons/drivers/mag_deck/__init__.py +6 -0
- opentrons/drivers/mag_deck/abstract.py +44 -0
- opentrons/drivers/mag_deck/driver.py +208 -0
- opentrons/drivers/mag_deck/simulator.py +63 -0
- opentrons/drivers/rpi_drivers/__init__.py +33 -0
- opentrons/drivers/rpi_drivers/dev_types.py +94 -0
- opentrons/drivers/rpi_drivers/gpio.py +282 -0
- opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
- opentrons/drivers/rpi_drivers/interfaces.py +15 -0
- opentrons/drivers/rpi_drivers/types.py +364 -0
- opentrons/drivers/rpi_drivers/usb.py +102 -0
- opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
- opentrons/drivers/serial_communication.py +151 -0
- opentrons/drivers/smoothie_drivers/__init__.py +4 -0
- opentrons/drivers/smoothie_drivers/connection.py +51 -0
- opentrons/drivers/smoothie_drivers/constants.py +121 -0
- opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
- opentrons/drivers/smoothie_drivers/errors.py +49 -0
- opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
- opentrons/drivers/smoothie_drivers/simulator.py +99 -0
- opentrons/drivers/smoothie_drivers/types.py +16 -0
- opentrons/drivers/temp_deck/__init__.py +10 -0
- opentrons/drivers/temp_deck/abstract.py +54 -0
- opentrons/drivers/temp_deck/driver.py +197 -0
- opentrons/drivers/temp_deck/simulator.py +57 -0
- opentrons/drivers/thermocycler/__init__.py +12 -0
- opentrons/drivers/thermocycler/abstract.py +99 -0
- opentrons/drivers/thermocycler/driver.py +395 -0
- opentrons/drivers/thermocycler/simulator.py +126 -0
- opentrons/drivers/types.py +107 -0
- opentrons/drivers/utils.py +222 -0
- opentrons/execute.py +742 -0
- opentrons/hardware_control/__init__.py +65 -0
- opentrons/hardware_control/__main__.py +77 -0
- opentrons/hardware_control/adapters.py +98 -0
- opentrons/hardware_control/api.py +1347 -0
- opentrons/hardware_control/backends/__init__.py +7 -0
- opentrons/hardware_control/backends/controller.py +400 -0
- opentrons/hardware_control/backends/errors.py +9 -0
- opentrons/hardware_control/backends/estop_state.py +164 -0
- opentrons/hardware_control/backends/flex_protocol.py +497 -0
- opentrons/hardware_control/backends/ot3controller.py +1930 -0
- opentrons/hardware_control/backends/ot3simulator.py +900 -0
- opentrons/hardware_control/backends/ot3utils.py +664 -0
- opentrons/hardware_control/backends/simulator.py +442 -0
- opentrons/hardware_control/backends/status_bar_state.py +240 -0
- opentrons/hardware_control/backends/subsystem_manager.py +431 -0
- opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
- opentrons/hardware_control/backends/types.py +14 -0
- opentrons/hardware_control/constants.py +6 -0
- opentrons/hardware_control/dev_types.py +125 -0
- opentrons/hardware_control/emulation/__init__.py +0 -0
- opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
- opentrons/hardware_control/emulation/app.py +56 -0
- opentrons/hardware_control/emulation/connection_handler.py +38 -0
- opentrons/hardware_control/emulation/heater_shaker.py +150 -0
- opentrons/hardware_control/emulation/magdeck.py +60 -0
- opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
- opentrons/hardware_control/emulation/module_server/client.py +78 -0
- opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
- opentrons/hardware_control/emulation/module_server/models.py +31 -0
- opentrons/hardware_control/emulation/module_server/server.py +110 -0
- opentrons/hardware_control/emulation/parser.py +74 -0
- opentrons/hardware_control/emulation/proxy.py +241 -0
- opentrons/hardware_control/emulation/run_emulator.py +68 -0
- opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
- opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
- opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
- opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
- opentrons/hardware_control/emulation/settings.py +119 -0
- opentrons/hardware_control/emulation/simulations.py +133 -0
- opentrons/hardware_control/emulation/smoothie.py +192 -0
- opentrons/hardware_control/emulation/tempdeck.py +69 -0
- opentrons/hardware_control/emulation/thermocycler.py +128 -0
- opentrons/hardware_control/emulation/types.py +10 -0
- opentrons/hardware_control/emulation/util.py +38 -0
- opentrons/hardware_control/errors.py +43 -0
- opentrons/hardware_control/execution_manager.py +164 -0
- opentrons/hardware_control/instruments/__init__.py +5 -0
- opentrons/hardware_control/instruments/instrument_abc.py +39 -0
- opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
- opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
- opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
- opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
- opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
- opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
- opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
- opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
- opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
- opentrons/hardware_control/module_control.py +332 -0
- opentrons/hardware_control/modules/__init__.py +69 -0
- opentrons/hardware_control/modules/absorbance_reader.py +373 -0
- opentrons/hardware_control/modules/errors.py +7 -0
- opentrons/hardware_control/modules/flex_stacker.py +948 -0
- opentrons/hardware_control/modules/heater_shaker.py +426 -0
- opentrons/hardware_control/modules/lid_temp_status.py +35 -0
- opentrons/hardware_control/modules/magdeck.py +233 -0
- opentrons/hardware_control/modules/mod_abc.py +245 -0
- opentrons/hardware_control/modules/module_calibration.py +93 -0
- opentrons/hardware_control/modules/plate_temp_status.py +61 -0
- opentrons/hardware_control/modules/tempdeck.py +299 -0
- opentrons/hardware_control/modules/thermocycler.py +731 -0
- opentrons/hardware_control/modules/types.py +417 -0
- opentrons/hardware_control/modules/update.py +255 -0
- opentrons/hardware_control/modules/utils.py +73 -0
- opentrons/hardware_control/motion_utilities.py +318 -0
- opentrons/hardware_control/nozzle_manager.py +422 -0
- opentrons/hardware_control/ot3_calibration.py +1171 -0
- opentrons/hardware_control/ot3api.py +3227 -0
- opentrons/hardware_control/pause_manager.py +31 -0
- opentrons/hardware_control/poller.py +112 -0
- opentrons/hardware_control/protocols/__init__.py +106 -0
- opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
- opentrons/hardware_control/protocols/calibratable.py +45 -0
- opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
- opentrons/hardware_control/protocols/configurable.py +48 -0
- opentrons/hardware_control/protocols/event_sourcer.py +18 -0
- opentrons/hardware_control/protocols/execution_controllable.py +33 -0
- opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
- opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
- opentrons/hardware_control/protocols/gripper_controller.py +55 -0
- opentrons/hardware_control/protocols/hardware_manager.py +51 -0
- opentrons/hardware_control/protocols/identifiable.py +16 -0
- opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
- opentrons/hardware_control/protocols/liquid_handler.py +266 -0
- opentrons/hardware_control/protocols/module_provider.py +16 -0
- opentrons/hardware_control/protocols/motion_controller.py +243 -0
- opentrons/hardware_control/protocols/position_estimator.py +45 -0
- opentrons/hardware_control/protocols/simulatable.py +10 -0
- opentrons/hardware_control/protocols/stoppable.py +9 -0
- opentrons/hardware_control/protocols/types.py +27 -0
- opentrons/hardware_control/robot_calibration.py +224 -0
- opentrons/hardware_control/scripts/README.md +28 -0
- opentrons/hardware_control/scripts/__init__.py +1 -0
- opentrons/hardware_control/scripts/gripper_control.py +208 -0
- opentrons/hardware_control/scripts/ot3gripper +7 -0
- opentrons/hardware_control/scripts/ot3repl +7 -0
- opentrons/hardware_control/scripts/repl.py +187 -0
- opentrons/hardware_control/scripts/tc_control.py +97 -0
- opentrons/hardware_control/scripts/update_module_fw.py +274 -0
- opentrons/hardware_control/simulator_setup.py +260 -0
- opentrons/hardware_control/thread_manager.py +431 -0
- opentrons/hardware_control/threaded_async_lock.py +97 -0
- opentrons/hardware_control/types.py +792 -0
- opentrons/hardware_control/util.py +234 -0
- opentrons/legacy_broker.py +53 -0
- opentrons/legacy_commands/__init__.py +1 -0
- opentrons/legacy_commands/commands.py +483 -0
- opentrons/legacy_commands/helpers.py +153 -0
- opentrons/legacy_commands/module_commands.py +276 -0
- opentrons/legacy_commands/protocol_commands.py +54 -0
- opentrons/legacy_commands/publisher.py +155 -0
- opentrons/legacy_commands/robot_commands.py +51 -0
- opentrons/legacy_commands/types.py +1186 -0
- opentrons/motion_planning/__init__.py +32 -0
- opentrons/motion_planning/adjacent_slots_getters.py +168 -0
- opentrons/motion_planning/deck_conflict.py +501 -0
- opentrons/motion_planning/errors.py +35 -0
- opentrons/motion_planning/types.py +42 -0
- opentrons/motion_planning/waypoints.py +218 -0
- opentrons/ordered_set.py +138 -0
- opentrons/protocol_api/__init__.py +105 -0
- opentrons/protocol_api/_liquid.py +157 -0
- opentrons/protocol_api/_liquid_properties.py +814 -0
- opentrons/protocol_api/_nozzle_layout.py +31 -0
- opentrons/protocol_api/_parameter_context.py +300 -0
- opentrons/protocol_api/_parameters.py +31 -0
- opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
- opentrons/protocol_api/_types.py +43 -0
- opentrons/protocol_api/config.py +23 -0
- opentrons/protocol_api/core/__init__.py +23 -0
- opentrons/protocol_api/core/common.py +33 -0
- opentrons/protocol_api/core/core_map.py +74 -0
- opentrons/protocol_api/core/engine/__init__.py +22 -0
- opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
- opentrons/protocol_api/core/engine/deck_conflict.py +400 -0
- opentrons/protocol_api/core/engine/exceptions.py +19 -0
- opentrons/protocol_api/core/engine/instrument.py +2391 -0
- opentrons/protocol_api/core/engine/labware.py +238 -0
- opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
- opentrons/protocol_api/core/engine/module_core.py +1027 -0
- opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
- opentrons/protocol_api/core/engine/point_calculations.py +64 -0
- opentrons/protocol_api/core/engine/protocol.py +1153 -0
- opentrons/protocol_api/core/engine/robot.py +139 -0
- opentrons/protocol_api/core/engine/stringify.py +74 -0
- opentrons/protocol_api/core/engine/transfer_components_executor.py +1006 -0
- opentrons/protocol_api/core/engine/well.py +241 -0
- opentrons/protocol_api/core/instrument.py +459 -0
- opentrons/protocol_api/core/labware.py +151 -0
- opentrons/protocol_api/core/legacy/__init__.py +11 -0
- opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
- opentrons/protocol_api/core/legacy/deck.py +369 -0
- opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
- opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
- opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
- opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
- opentrons/protocol_api/core/legacy/load_info.py +67 -0
- opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
- opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
- opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
- opentrons/protocol_api/core/module.py +484 -0
- opentrons/protocol_api/core/protocol.py +311 -0
- opentrons/protocol_api/core/robot.py +51 -0
- opentrons/protocol_api/core/well.py +116 -0
- opentrons/protocol_api/core/well_grid.py +45 -0
- opentrons/protocol_api/create_protocol_context.py +177 -0
- opentrons/protocol_api/deck.py +223 -0
- opentrons/protocol_api/disposal_locations.py +244 -0
- opentrons/protocol_api/instrument_context.py +3272 -0
- opentrons/protocol_api/labware.py +1579 -0
- opentrons/protocol_api/module_contexts.py +1447 -0
- opentrons/protocol_api/module_validation_and_errors.py +61 -0
- opentrons/protocol_api/protocol_context.py +1688 -0
- opentrons/protocol_api/robot_context.py +303 -0
- opentrons/protocol_api/validation.py +761 -0
- opentrons/protocol_engine/__init__.py +155 -0
- opentrons/protocol_engine/actions/__init__.py +65 -0
- opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
- opentrons/protocol_engine/actions/action_handler.py +13 -0
- opentrons/protocol_engine/actions/actions.py +302 -0
- opentrons/protocol_engine/actions/get_state_update.py +38 -0
- opentrons/protocol_engine/clients/__init__.py +5 -0
- opentrons/protocol_engine/clients/sync_client.py +174 -0
- opentrons/protocol_engine/clients/transports.py +197 -0
- opentrons/protocol_engine/commands/__init__.py +757 -0
- opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
- opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
- opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
- opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
- opentrons/protocol_engine/commands/aspirate.py +244 -0
- opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
- opentrons/protocol_engine/commands/blow_out.py +146 -0
- opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
- opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
- opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
- opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
- opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
- opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
- opentrons/protocol_engine/commands/command.py +308 -0
- opentrons/protocol_engine/commands/command_unions.py +974 -0
- opentrons/protocol_engine/commands/comment.py +57 -0
- opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
- opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
- opentrons/protocol_engine/commands/custom.py +67 -0
- opentrons/protocol_engine/commands/dispense.py +194 -0
- opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
- opentrons/protocol_engine/commands/drop_tip.py +232 -0
- opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
- opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
- opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
- opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
- opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
- opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
- opentrons/protocol_engine/commands/flex_stacker/store.py +339 -0
- opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
- opentrons/protocol_engine/commands/get_next_tip.py +134 -0
- opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
- opentrons/protocol_engine/commands/hash_command_params.py +38 -0
- opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
- opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
- opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
- opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
- opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
- opentrons/protocol_engine/commands/home.py +100 -0
- opentrons/protocol_engine/commands/identify_module.py +86 -0
- opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
- opentrons/protocol_engine/commands/liquid_probe.py +464 -0
- opentrons/protocol_engine/commands/load_labware.py +210 -0
- opentrons/protocol_engine/commands/load_lid.py +154 -0
- opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
- opentrons/protocol_engine/commands/load_liquid.py +95 -0
- opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
- opentrons/protocol_engine/commands/load_module.py +223 -0
- opentrons/protocol_engine/commands/load_pipette.py +167 -0
- opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
- opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
- opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
- opentrons/protocol_engine/commands/move_labware.py +546 -0
- opentrons/protocol_engine/commands/move_relative.py +102 -0
- opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
- opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
- opentrons/protocol_engine/commands/move_to_well.py +119 -0
- opentrons/protocol_engine/commands/movement_common.py +338 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
- opentrons/protocol_engine/commands/pipetting_common.py +443 -0
- opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
- opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
- opentrons/protocol_engine/commands/reload_labware.py +90 -0
- opentrons/protocol_engine/commands/retract_axis.py +75 -0
- opentrons/protocol_engine/commands/robot/__init__.py +70 -0
- opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -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 +86 -0
- opentrons/protocol_engine/commands/save_position.py +109 -0
- opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
- opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
- opentrons/protocol_engine/commands/set_status_bar.py +89 -0
- opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
- opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
- opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
- opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
- opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
- opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
- opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
- opentrons/protocol_engine/commands/touch_tip.py +189 -0
- opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
- opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
- opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
- opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
- opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
- opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
- opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
- opentrons/protocol_engine/create_protocol_engine.py +193 -0
- opentrons/protocol_engine/engine_support.py +28 -0
- opentrons/protocol_engine/error_recovery_policy.py +81 -0
- opentrons/protocol_engine/errors/__init__.py +191 -0
- opentrons/protocol_engine/errors/error_occurrence.py +182 -0
- opentrons/protocol_engine/errors/exceptions.py +1308 -0
- opentrons/protocol_engine/execution/__init__.py +50 -0
- opentrons/protocol_engine/execution/command_executor.py +216 -0
- opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
- opentrons/protocol_engine/execution/door_watcher.py +119 -0
- opentrons/protocol_engine/execution/equipment.py +819 -0
- opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
- opentrons/protocol_engine/execution/gantry_mover.py +686 -0
- opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
- opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
- opentrons/protocol_engine/execution/labware_movement.py +297 -0
- opentrons/protocol_engine/execution/movement.py +350 -0
- opentrons/protocol_engine/execution/pipetting.py +607 -0
- opentrons/protocol_engine/execution/queue_worker.py +86 -0
- opentrons/protocol_engine/execution/rail_lights.py +25 -0
- opentrons/protocol_engine/execution/run_control.py +33 -0
- opentrons/protocol_engine/execution/status_bar.py +34 -0
- opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
- opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
- opentrons/protocol_engine/execution/tip_handler.py +550 -0
- opentrons/protocol_engine/labware_offset_standardization.py +194 -0
- opentrons/protocol_engine/notes/__init__.py +17 -0
- opentrons/protocol_engine/notes/notes.py +59 -0
- opentrons/protocol_engine/plugins.py +104 -0
- opentrons/protocol_engine/protocol_engine.py +683 -0
- opentrons/protocol_engine/resources/__init__.py +26 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
- opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
- opentrons/protocol_engine/resources/file_provider.py +161 -0
- opentrons/protocol_engine/resources/fixture_validation.py +68 -0
- opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
- opentrons/protocol_engine/resources/labware_validation.py +73 -0
- opentrons/protocol_engine/resources/model_utils.py +32 -0
- opentrons/protocol_engine/resources/module_data_provider.py +44 -0
- opentrons/protocol_engine/resources/ot3_validation.py +21 -0
- opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
- opentrons/protocol_engine/slot_standardization.py +128 -0
- opentrons/protocol_engine/state/__init__.py +1 -0
- opentrons/protocol_engine/state/_abstract_store.py +27 -0
- opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
- opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
- opentrons/protocol_engine/state/_move_types.py +83 -0
- opentrons/protocol_engine/state/_well_math.py +193 -0
- opentrons/protocol_engine/state/addressable_areas.py +699 -0
- opentrons/protocol_engine/state/command_history.py +309 -0
- opentrons/protocol_engine/state/commands.py +1164 -0
- opentrons/protocol_engine/state/config.py +39 -0
- opentrons/protocol_engine/state/files.py +57 -0
- opentrons/protocol_engine/state/fluid_stack.py +138 -0
- opentrons/protocol_engine/state/geometry.py +2408 -0
- opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
- opentrons/protocol_engine/state/labware.py +1432 -0
- opentrons/protocol_engine/state/liquid_classes.py +82 -0
- opentrons/protocol_engine/state/liquids.py +73 -0
- opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
- opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
- opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
- opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
- opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
- opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
- opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
- opentrons/protocol_engine/state/modules.py +1515 -0
- opentrons/protocol_engine/state/motion.py +373 -0
- opentrons/protocol_engine/state/pipettes.py +905 -0
- opentrons/protocol_engine/state/state.py +421 -0
- opentrons/protocol_engine/state/state_summary.py +36 -0
- opentrons/protocol_engine/state/tips.py +420 -0
- opentrons/protocol_engine/state/update_types.py +904 -0
- opentrons/protocol_engine/state/wells.py +290 -0
- opentrons/protocol_engine/types/__init__.py +310 -0
- opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
- opentrons/protocol_engine/types/command_annotations.py +53 -0
- opentrons/protocol_engine/types/deck_configuration.py +81 -0
- opentrons/protocol_engine/types/execution.py +96 -0
- opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
- opentrons/protocol_engine/types/instrument.py +47 -0
- opentrons/protocol_engine/types/instrument_sensors.py +47 -0
- opentrons/protocol_engine/types/labware.py +131 -0
- opentrons/protocol_engine/types/labware_movement.py +22 -0
- opentrons/protocol_engine/types/labware_offset_location.py +111 -0
- opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
- opentrons/protocol_engine/types/liquid.py +40 -0
- opentrons/protocol_engine/types/liquid_class.py +59 -0
- opentrons/protocol_engine/types/liquid_handling.py +13 -0
- opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
- opentrons/protocol_engine/types/location.py +194 -0
- opentrons/protocol_engine/types/module.py +310 -0
- opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
- opentrons/protocol_engine/types/run_time_parameters.py +133 -0
- opentrons/protocol_engine/types/tip.py +18 -0
- opentrons/protocol_engine/types/util.py +21 -0
- opentrons/protocol_engine/types/well_position.py +124 -0
- opentrons/protocol_reader/__init__.py +37 -0
- opentrons/protocol_reader/extract_labware_definitions.py +66 -0
- opentrons/protocol_reader/file_format_validator.py +152 -0
- opentrons/protocol_reader/file_hasher.py +27 -0
- opentrons/protocol_reader/file_identifier.py +284 -0
- opentrons/protocol_reader/file_reader_writer.py +90 -0
- opentrons/protocol_reader/input_file.py +16 -0
- opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
- opentrons/protocol_reader/protocol_reader.py +188 -0
- opentrons/protocol_reader/protocol_source.py +124 -0
- opentrons/protocol_reader/role_analyzer.py +86 -0
- opentrons/protocol_runner/__init__.py +26 -0
- opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
- opentrons/protocol_runner/json_file_reader.py +55 -0
- opentrons/protocol_runner/json_translator.py +314 -0
- opentrons/protocol_runner/legacy_command_mapper.py +852 -0
- opentrons/protocol_runner/legacy_context_plugin.py +116 -0
- opentrons/protocol_runner/protocol_runner.py +530 -0
- opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
- opentrons/protocol_runner/run_orchestrator.py +496 -0
- opentrons/protocol_runner/task_queue.py +95 -0
- opentrons/protocols/__init__.py +6 -0
- opentrons/protocols/advanced_control/__init__.py +0 -0
- opentrons/protocols/advanced_control/common.py +38 -0
- opentrons/protocols/advanced_control/mix.py +60 -0
- opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
- opentrons/protocols/advanced_control/transfers/common.py +180 -0
- opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
- opentrons/protocols/api_support/__init__.py +0 -0
- opentrons/protocols/api_support/constants.py +8 -0
- opentrons/protocols/api_support/deck_type.py +110 -0
- opentrons/protocols/api_support/definitions.py +18 -0
- opentrons/protocols/api_support/instrument.py +151 -0
- opentrons/protocols/api_support/labware_like.py +233 -0
- opentrons/protocols/api_support/tip_tracker.py +175 -0
- opentrons/protocols/api_support/types.py +32 -0
- opentrons/protocols/api_support/util.py +403 -0
- opentrons/protocols/bundle.py +89 -0
- opentrons/protocols/duration/__init__.py +4 -0
- opentrons/protocols/duration/errors.py +5 -0
- opentrons/protocols/duration/estimator.py +628 -0
- opentrons/protocols/execution/__init__.py +0 -0
- opentrons/protocols/execution/dev_types.py +181 -0
- opentrons/protocols/execution/errors.py +40 -0
- opentrons/protocols/execution/execute.py +84 -0
- opentrons/protocols/execution/execute_json_v3.py +275 -0
- opentrons/protocols/execution/execute_json_v4.py +359 -0
- opentrons/protocols/execution/execute_json_v5.py +28 -0
- opentrons/protocols/execution/execute_python.py +169 -0
- opentrons/protocols/execution/json_dispatchers.py +87 -0
- opentrons/protocols/execution/types.py +7 -0
- opentrons/protocols/geometry/__init__.py +0 -0
- opentrons/protocols/geometry/planning.py +297 -0
- opentrons/protocols/labware.py +312 -0
- opentrons/protocols/models/__init__.py +0 -0
- opentrons/protocols/models/json_protocol.py +679 -0
- opentrons/protocols/parameters/__init__.py +0 -0
- opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
- opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
- opentrons/protocols/parameters/exceptions.py +34 -0
- opentrons/protocols/parameters/parameter_definition.py +272 -0
- opentrons/protocols/parameters/types.py +17 -0
- opentrons/protocols/parameters/validation.py +267 -0
- opentrons/protocols/parse.py +671 -0
- opentrons/protocols/types.py +159 -0
- opentrons/py.typed +0 -0
- opentrons/resources/scripts/lpc21isp +0 -0
- opentrons/resources/smoothie-edge-8414642.hex +23010 -0
- opentrons/simulate.py +1065 -0
- opentrons/system/__init__.py +6 -0
- opentrons/system/camera.py +51 -0
- opentrons/system/log_control.py +59 -0
- opentrons/system/nmcli.py +856 -0
- opentrons/system/resin.py +24 -0
- opentrons/system/smoothie_update.py +15 -0
- opentrons/system/wifi.py +204 -0
- opentrons/tools/__init__.py +0 -0
- opentrons/tools/args_handler.py +22 -0
- opentrons/tools/write_pipette_memory.py +157 -0
- opentrons/types.py +618 -0
- opentrons/util/__init__.py +1 -0
- opentrons/util/async_helpers.py +166 -0
- opentrons/util/broker.py +84 -0
- opentrons/util/change_notifier.py +47 -0
- opentrons/util/entrypoint_util.py +278 -0
- opentrons/util/get_union_elements.py +26 -0
- opentrons/util/helpers.py +6 -0
- opentrons/util/linal.py +178 -0
- opentrons/util/logging_config.py +265 -0
- opentrons/util/logging_queue_handler.py +61 -0
- opentrons/util/performance_helpers.py +157 -0
- opentrons-8.6.0.dist-info/METADATA +37 -0
- opentrons-8.6.0.dist-info/RECORD +601 -0
- opentrons-8.6.0.dist-info/WHEEL +4 -0
- opentrons-8.6.0.dist-info/entry_points.txt +3 -0
- opentrons-8.6.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,1432 @@
|
|
|
1
|
+
"""Basic labware data state and store."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Dict,
|
|
9
|
+
List,
|
|
10
|
+
Mapping,
|
|
11
|
+
Optional,
|
|
12
|
+
Sequence,
|
|
13
|
+
Tuple,
|
|
14
|
+
NamedTuple,
|
|
15
|
+
Union,
|
|
16
|
+
overload,
|
|
17
|
+
)
|
|
18
|
+
from typing_extensions import assert_never
|
|
19
|
+
|
|
20
|
+
from opentrons.protocol_engine.state import update_types
|
|
21
|
+
from opentrons_shared_data.deck.types import DeckDefinitionV5
|
|
22
|
+
from opentrons_shared_data.gripper.constants import LABWARE_GRIP_FORCE
|
|
23
|
+
from opentrons_shared_data.labware.labware_definition import (
|
|
24
|
+
InnerWellGeometry,
|
|
25
|
+
LabwareDefinition,
|
|
26
|
+
LabwareDefinition2,
|
|
27
|
+
LabwareRole,
|
|
28
|
+
WellDefinition2,
|
|
29
|
+
WellDefinition3,
|
|
30
|
+
UserDefinedVolumes,
|
|
31
|
+
)
|
|
32
|
+
from opentrons_shared_data.pipette.types import LabwareUri
|
|
33
|
+
|
|
34
|
+
from opentrons.protocol_engine.state._axis_aligned_bounding_box import (
|
|
35
|
+
AxisAlignedBoundingBox3D,
|
|
36
|
+
)
|
|
37
|
+
from opentrons.types import DeckSlotName, StagingSlotName, MountType, Point
|
|
38
|
+
from opentrons.protocols.api_support.constants import OPENTRONS_NAMESPACE
|
|
39
|
+
from opentrons.calibration_storage.helpers import uri_from_details
|
|
40
|
+
|
|
41
|
+
from .. import errors
|
|
42
|
+
from ..resources import DeckFixedLabware, labware_validation, fixture_validation
|
|
43
|
+
from ..types import (
|
|
44
|
+
DeckSlotLocation,
|
|
45
|
+
OnLabwareLocation,
|
|
46
|
+
AddressableAreaLocation,
|
|
47
|
+
NonStackedLocation,
|
|
48
|
+
Dimensions,
|
|
49
|
+
LabwareOffset,
|
|
50
|
+
LabwareOffsetVector,
|
|
51
|
+
LabwareOffsetLocationSequence,
|
|
52
|
+
LegacyLabwareOffsetLocation,
|
|
53
|
+
InStackerHopperLocation,
|
|
54
|
+
LabwareLocation,
|
|
55
|
+
LoadedLabware,
|
|
56
|
+
ModuleLocation,
|
|
57
|
+
OverlapOffset,
|
|
58
|
+
LabwareMovementOffsetData,
|
|
59
|
+
OnDeckLabwareLocation,
|
|
60
|
+
OFF_DECK_LOCATION,
|
|
61
|
+
)
|
|
62
|
+
from ..actions import (
|
|
63
|
+
Action,
|
|
64
|
+
AddLabwareOffsetAction,
|
|
65
|
+
AddLabwareDefinitionAction,
|
|
66
|
+
get_state_updates,
|
|
67
|
+
)
|
|
68
|
+
from ._abstract_store import HasState, HandlesActions
|
|
69
|
+
from ._move_types import EdgePathType
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# URIs of labware whose definitions accidentally specify an engage height
|
|
73
|
+
# in units of half-millimeters instead of millimeters.
|
|
74
|
+
_MAGDECK_HALF_MM_LABWARE = {
|
|
75
|
+
"opentrons/biorad_96_wellplate_200ul_pcr/1",
|
|
76
|
+
"opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1",
|
|
77
|
+
"opentrons/usascientific_96_wellplate_2.4ml_deep/1",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_RIGHT_SIDE_SLOTS = {
|
|
81
|
+
# OT-2:
|
|
82
|
+
DeckSlotName.FIXED_TRASH,
|
|
83
|
+
DeckSlotName.SLOT_9,
|
|
84
|
+
DeckSlotName.SLOT_6,
|
|
85
|
+
DeckSlotName.SLOT_3,
|
|
86
|
+
# OT-3:
|
|
87
|
+
DeckSlotName.SLOT_A3,
|
|
88
|
+
DeckSlotName.SLOT_B3,
|
|
89
|
+
DeckSlotName.SLOT_C3,
|
|
90
|
+
DeckSlotName.SLOT_D3,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# The max height of the labware that can fit in a plate reader
|
|
95
|
+
_PLATE_READER_MAX_LABWARE_Z_MM = 16.0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
_WellDefinition = WellDefinition2 | WellDefinition3
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class LabwareLoadParams(NamedTuple):
|
|
102
|
+
"""Parameters required to load a labware in Protocol Engine."""
|
|
103
|
+
|
|
104
|
+
load_name: str
|
|
105
|
+
namespace: str
|
|
106
|
+
version: int
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class LabwareState:
|
|
111
|
+
"""State of all loaded labware resources."""
|
|
112
|
+
|
|
113
|
+
# Indexed by LoadedLabware.id.
|
|
114
|
+
# If a LoadedLabware here has a non-None offsetId,
|
|
115
|
+
# it must point to an existing element of labware_offsets_by_id.
|
|
116
|
+
labware_by_id: Dict[str, LoadedLabware]
|
|
117
|
+
|
|
118
|
+
# Indexed by LabwareOffset.id.
|
|
119
|
+
# We rely on Python 3.7+ preservation of dict insertion order.
|
|
120
|
+
labware_offsets_by_id: Dict[str, LabwareOffset]
|
|
121
|
+
|
|
122
|
+
definitions_by_uri: Dict[str, LabwareDefinition]
|
|
123
|
+
deck_definition: DeckDefinitionV5
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class LabwareStore(HasState[LabwareState], HandlesActions):
|
|
127
|
+
"""Labware state container."""
|
|
128
|
+
|
|
129
|
+
_state: LabwareState
|
|
130
|
+
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
deck_definition: DeckDefinitionV5,
|
|
134
|
+
deck_fixed_labware: Sequence[DeckFixedLabware],
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Initialize a labware store and its state."""
|
|
137
|
+
definitions_by_uri: Dict[str, LabwareDefinition] = {
|
|
138
|
+
uri_from_details(
|
|
139
|
+
load_name=fixed_labware.definition.parameters.loadName,
|
|
140
|
+
namespace=fixed_labware.definition.namespace,
|
|
141
|
+
version=fixed_labware.definition.version,
|
|
142
|
+
): fixed_labware.definition
|
|
143
|
+
for fixed_labware in deck_fixed_labware
|
|
144
|
+
}
|
|
145
|
+
labware_by_id = {
|
|
146
|
+
fixed_labware.labware_id: LoadedLabware.model_construct(
|
|
147
|
+
id=fixed_labware.labware_id,
|
|
148
|
+
location=fixed_labware.location,
|
|
149
|
+
loadName=fixed_labware.definition.parameters.loadName,
|
|
150
|
+
definitionUri=uri_from_details(
|
|
151
|
+
load_name=fixed_labware.definition.parameters.loadName,
|
|
152
|
+
namespace=fixed_labware.definition.namespace,
|
|
153
|
+
version=fixed_labware.definition.version,
|
|
154
|
+
),
|
|
155
|
+
offsetId=None,
|
|
156
|
+
)
|
|
157
|
+
for fixed_labware in deck_fixed_labware
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
self._state = LabwareState(
|
|
161
|
+
definitions_by_uri=definitions_by_uri,
|
|
162
|
+
labware_offsets_by_id={},
|
|
163
|
+
labware_by_id=labware_by_id,
|
|
164
|
+
deck_definition=deck_definition,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def handle_action(self, action: Action) -> None:
|
|
168
|
+
"""Modify state in reaction to an action."""
|
|
169
|
+
for state_update in get_state_updates(action):
|
|
170
|
+
self._add_loaded_labware(state_update)
|
|
171
|
+
self._add_batch_loaded_labwares(state_update)
|
|
172
|
+
self._add_loaded_lid_stack(state_update)
|
|
173
|
+
self._set_labware_location(state_update)
|
|
174
|
+
self._set_batch_labware_location(state_update)
|
|
175
|
+
self._set_labware_lid(state_update)
|
|
176
|
+
|
|
177
|
+
if isinstance(action, AddLabwareOffsetAction):
|
|
178
|
+
labware_offset = LabwareOffset.model_construct(
|
|
179
|
+
id=action.labware_offset_id,
|
|
180
|
+
createdAt=action.created_at,
|
|
181
|
+
definitionUri=action.request.definitionUri,
|
|
182
|
+
location=action.request.legacyLocation,
|
|
183
|
+
locationSequence=action.request.locationSequence,
|
|
184
|
+
vector=action.request.vector,
|
|
185
|
+
)
|
|
186
|
+
self._add_labware_offset(labware_offset)
|
|
187
|
+
|
|
188
|
+
elif isinstance(action, AddLabwareDefinitionAction):
|
|
189
|
+
uri = uri_from_details(
|
|
190
|
+
namespace=action.definition.namespace,
|
|
191
|
+
load_name=action.definition.parameters.loadName,
|
|
192
|
+
version=action.definition.version,
|
|
193
|
+
)
|
|
194
|
+
self._state.definitions_by_uri[uri] = action.definition
|
|
195
|
+
|
|
196
|
+
def _add_labware_offset(self, labware_offset: LabwareOffset) -> None:
|
|
197
|
+
"""Add a new labware offset to state.
|
|
198
|
+
|
|
199
|
+
`labware_offset.id` must not match any existing labware offset ID.
|
|
200
|
+
`LoadLabwareCommand`s retain references to their corresponding labware offsets
|
|
201
|
+
and expect them to be immutable.
|
|
202
|
+
"""
|
|
203
|
+
assert labware_offset.id not in self._state.labware_offsets_by_id
|
|
204
|
+
|
|
205
|
+
self._state.labware_offsets_by_id[labware_offset.id] = labware_offset
|
|
206
|
+
|
|
207
|
+
def _add_loaded_labware(self, state_update: update_types.StateUpdate) -> None:
|
|
208
|
+
loaded_labware_update = state_update.loaded_labware
|
|
209
|
+
if loaded_labware_update != update_types.NO_CHANGE:
|
|
210
|
+
# If the labware load refers to an offset, that offset must actually exist.
|
|
211
|
+
if loaded_labware_update.offset_id is not None:
|
|
212
|
+
assert (
|
|
213
|
+
loaded_labware_update.offset_id in self._state.labware_offsets_by_id
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
definition_uri = uri_from_details(
|
|
217
|
+
namespace=loaded_labware_update.definition.namespace,
|
|
218
|
+
load_name=loaded_labware_update.definition.parameters.loadName,
|
|
219
|
+
version=loaded_labware_update.definition.version,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
self._state.definitions_by_uri[
|
|
223
|
+
definition_uri
|
|
224
|
+
] = loaded_labware_update.definition
|
|
225
|
+
|
|
226
|
+
location = loaded_labware_update.new_location
|
|
227
|
+
|
|
228
|
+
display_name = loaded_labware_update.display_name
|
|
229
|
+
|
|
230
|
+
self._state.labware_by_id[
|
|
231
|
+
loaded_labware_update.labware_id
|
|
232
|
+
] = LoadedLabware.model_construct(
|
|
233
|
+
id=loaded_labware_update.labware_id,
|
|
234
|
+
location=location,
|
|
235
|
+
loadName=loaded_labware_update.definition.parameters.loadName,
|
|
236
|
+
definitionUri=definition_uri,
|
|
237
|
+
offsetId=loaded_labware_update.offset_id,
|
|
238
|
+
displayName=display_name,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def _add_batch_loaded_labwares(
|
|
242
|
+
self, state_update: update_types.StateUpdate
|
|
243
|
+
) -> None:
|
|
244
|
+
batch_loaded_labware_update = state_update.batch_loaded_labware
|
|
245
|
+
if batch_loaded_labware_update == update_types.NO_CHANGE:
|
|
246
|
+
return
|
|
247
|
+
# If the labware load refers to an offset, that offset must actually exist.
|
|
248
|
+
for labware_id in batch_loaded_labware_update.new_locations_by_id:
|
|
249
|
+
if batch_loaded_labware_update.offset_ids_by_id[labware_id] is not None:
|
|
250
|
+
assert (
|
|
251
|
+
batch_loaded_labware_update.offset_ids_by_id[labware_id]
|
|
252
|
+
in self._state.labware_offsets_by_id
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
definition_uri = uri_from_details(
|
|
256
|
+
namespace=batch_loaded_labware_update.definitions_by_id[
|
|
257
|
+
labware_id
|
|
258
|
+
].namespace,
|
|
259
|
+
load_name=batch_loaded_labware_update.definitions_by_id[
|
|
260
|
+
labware_id
|
|
261
|
+
].parameters.loadName,
|
|
262
|
+
version=batch_loaded_labware_update.definitions_by_id[
|
|
263
|
+
labware_id
|
|
264
|
+
].version,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
self._state.definitions_by_uri[
|
|
268
|
+
definition_uri
|
|
269
|
+
] = batch_loaded_labware_update.definitions_by_id[labware_id]
|
|
270
|
+
|
|
271
|
+
location = batch_loaded_labware_update.new_locations_by_id[labware_id]
|
|
272
|
+
|
|
273
|
+
self._state.labware_by_id[labware_id] = LoadedLabware.model_construct(
|
|
274
|
+
id=labware_id,
|
|
275
|
+
location=location,
|
|
276
|
+
loadName=batch_loaded_labware_update.definitions_by_id[
|
|
277
|
+
labware_id
|
|
278
|
+
].parameters.loadName,
|
|
279
|
+
definitionUri=definition_uri,
|
|
280
|
+
offsetId=batch_loaded_labware_update.offset_ids_by_id[labware_id],
|
|
281
|
+
displayName=batch_loaded_labware_update.display_names_by_id[labware_id],
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def _add_loaded_lid_stack(self, state_update: update_types.StateUpdate) -> None:
|
|
285
|
+
loaded_lid_stack_update = state_update.loaded_lid_stack
|
|
286
|
+
if loaded_lid_stack_update != update_types.NO_CHANGE:
|
|
287
|
+
# Add the stack object
|
|
288
|
+
stack_definition_uri = uri_from_details(
|
|
289
|
+
namespace=loaded_lid_stack_update.stack_object_definition.namespace,
|
|
290
|
+
load_name=loaded_lid_stack_update.stack_object_definition.parameters.loadName,
|
|
291
|
+
version=loaded_lid_stack_update.stack_object_definition.version,
|
|
292
|
+
)
|
|
293
|
+
self.state.definitions_by_uri[
|
|
294
|
+
stack_definition_uri
|
|
295
|
+
] = loaded_lid_stack_update.stack_object_definition
|
|
296
|
+
self._state.labware_by_id[
|
|
297
|
+
loaded_lid_stack_update.stack_id
|
|
298
|
+
] = LoadedLabware.construct(
|
|
299
|
+
id=loaded_lid_stack_update.stack_id,
|
|
300
|
+
location=loaded_lid_stack_update.stack_location,
|
|
301
|
+
loadName=loaded_lid_stack_update.stack_object_definition.parameters.loadName,
|
|
302
|
+
definitionUri=stack_definition_uri,
|
|
303
|
+
offsetId=None,
|
|
304
|
+
displayName=None,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Add the Lids on top of the stack object
|
|
308
|
+
for labware_id in loaded_lid_stack_update.new_locations_by_id:
|
|
309
|
+
if loaded_lid_stack_update.definition is None:
|
|
310
|
+
raise ValueError(
|
|
311
|
+
"Lid Stack Labware Definition cannot be None when multiple lids are loaded."
|
|
312
|
+
)
|
|
313
|
+
definition_uri = uri_from_details(
|
|
314
|
+
namespace=loaded_lid_stack_update.definition.namespace,
|
|
315
|
+
load_name=loaded_lid_stack_update.definition.parameters.loadName,
|
|
316
|
+
version=loaded_lid_stack_update.definition.version,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
self._state.definitions_by_uri[
|
|
320
|
+
definition_uri
|
|
321
|
+
] = loaded_lid_stack_update.definition
|
|
322
|
+
|
|
323
|
+
location = loaded_lid_stack_update.new_locations_by_id[labware_id]
|
|
324
|
+
|
|
325
|
+
self._state.labware_by_id[labware_id] = LoadedLabware.construct(
|
|
326
|
+
id=labware_id,
|
|
327
|
+
location=location,
|
|
328
|
+
loadName=loaded_lid_stack_update.definition.parameters.loadName,
|
|
329
|
+
definitionUri=definition_uri,
|
|
330
|
+
offsetId=None,
|
|
331
|
+
displayName=None,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def _set_labware_lid(self, state_update: update_types.StateUpdate) -> None:
|
|
335
|
+
labware_lid_update = state_update.labware_lid
|
|
336
|
+
if labware_lid_update != update_types.NO_CHANGE:
|
|
337
|
+
parent_labware_ids = labware_lid_update.parent_labware_ids
|
|
338
|
+
for i in range(len(parent_labware_ids)):
|
|
339
|
+
lid_id = labware_lid_update.lid_ids[i]
|
|
340
|
+
self._state.labware_by_id[parent_labware_ids[i]].lid_id = lid_id
|
|
341
|
+
|
|
342
|
+
def _do_update_labware_location(
|
|
343
|
+
self, labware_id: str, new_location: LabwareLocation, new_offset_id: str | None
|
|
344
|
+
) -> None:
|
|
345
|
+
self._state.labware_by_id[labware_id].offsetId = new_offset_id
|
|
346
|
+
|
|
347
|
+
if isinstance(new_location, AddressableAreaLocation) and (
|
|
348
|
+
fixture_validation.is_gripper_waste_chute(new_location.addressableAreaName)
|
|
349
|
+
or fixture_validation.is_trash(new_location.addressableAreaName)
|
|
350
|
+
):
|
|
351
|
+
# If a labware has been moved into a waste chute it's been chuted away and is now technically off deck
|
|
352
|
+
new_location = OFF_DECK_LOCATION
|
|
353
|
+
|
|
354
|
+
self._state.labware_by_id[labware_id].location = new_location
|
|
355
|
+
|
|
356
|
+
def _set_labware_location(self, state_update: update_types.StateUpdate) -> None:
|
|
357
|
+
labware_location_update = state_update.labware_location
|
|
358
|
+
if labware_location_update == update_types.NO_CHANGE:
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
self._do_update_labware_location(
|
|
362
|
+
labware_location_update.labware_id,
|
|
363
|
+
labware_location_update.new_location,
|
|
364
|
+
labware_location_update.offset_id,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def _set_batch_labware_location(
|
|
368
|
+
self, state_update: update_types.StateUpdate
|
|
369
|
+
) -> None:
|
|
370
|
+
batch_location_update = state_update.batch_labware_location
|
|
371
|
+
if batch_location_update == update_types.NO_CHANGE:
|
|
372
|
+
return
|
|
373
|
+
for (
|
|
374
|
+
labware_id,
|
|
375
|
+
new_location,
|
|
376
|
+
) in batch_location_update.new_locations_by_id.items():
|
|
377
|
+
self._do_update_labware_location(
|
|
378
|
+
labware_id,
|
|
379
|
+
new_location,
|
|
380
|
+
batch_location_update.new_offset_ids_by_id.get(labware_id, None),
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class LabwareView:
|
|
385
|
+
"""Read-only labware state view."""
|
|
386
|
+
|
|
387
|
+
_state: LabwareState
|
|
388
|
+
|
|
389
|
+
def __init__(self, state: LabwareState) -> None:
|
|
390
|
+
"""Initialize the computed view of labware state.
|
|
391
|
+
|
|
392
|
+
Arguments:
|
|
393
|
+
state: Labware state dataclass used for all calculations.
|
|
394
|
+
"""
|
|
395
|
+
self._state = state
|
|
396
|
+
|
|
397
|
+
def get(self, labware_id: str) -> LoadedLabware:
|
|
398
|
+
"""Get labware data by the labware's unique identifier."""
|
|
399
|
+
try:
|
|
400
|
+
return self._state.labware_by_id[labware_id]
|
|
401
|
+
except KeyError as e:
|
|
402
|
+
raise errors.LabwareNotLoadedError(
|
|
403
|
+
f"Labware {labware_id} not found."
|
|
404
|
+
) from e
|
|
405
|
+
|
|
406
|
+
def known(self, labware_id: str) -> bool:
|
|
407
|
+
"""Check if the labware specified by labware_id has been loaded."""
|
|
408
|
+
return labware_id in self._state.labware_by_id
|
|
409
|
+
|
|
410
|
+
def get_id_by_module(self, module_id: str) -> str:
|
|
411
|
+
"""Return the ID of the labware loaded on the given module."""
|
|
412
|
+
for labware_id, labware in self._state.labware_by_id.items():
|
|
413
|
+
if (
|
|
414
|
+
isinstance(labware.location, ModuleLocation)
|
|
415
|
+
and labware.location.moduleId == module_id
|
|
416
|
+
):
|
|
417
|
+
return labware_id
|
|
418
|
+
|
|
419
|
+
raise errors.exceptions.LabwareNotLoadedOnModuleError(
|
|
420
|
+
"There is no labware loaded on this Module"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
def get_id_by_labware(self, labware_id: str) -> str:
|
|
424
|
+
"""Return the ID of the labware loaded on the given labware."""
|
|
425
|
+
for labware in self._state.labware_by_id.values():
|
|
426
|
+
if (
|
|
427
|
+
isinstance(labware.location, OnLabwareLocation)
|
|
428
|
+
and labware.location.labwareId == labware_id
|
|
429
|
+
):
|
|
430
|
+
return labware.id
|
|
431
|
+
raise errors.exceptions.LabwareNotLoadedOnLabwareError(
|
|
432
|
+
f"There is not labware loaded onto labware {labware_id}"
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
def raise_if_labware_has_non_lid_labware_on_top(self, labware_id: str) -> None:
|
|
436
|
+
"""Raise if labware has another labware that is not its lid on top."""
|
|
437
|
+
lid_id = self.get_lid_id_by_labware_id(labware_id)
|
|
438
|
+
for candidate_id, candidate_labware in self._state.labware_by_id.items():
|
|
439
|
+
if (
|
|
440
|
+
isinstance(candidate_labware.location, OnLabwareLocation)
|
|
441
|
+
and candidate_labware.location.labwareId == labware_id
|
|
442
|
+
and candidate_id != lid_id
|
|
443
|
+
):
|
|
444
|
+
raise errors.LabwareIsInStackError(
|
|
445
|
+
f"Cannot access labware {labware_id} because it has a non-lid labware stacked on top."
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
def raise_if_labware_has_labware_on_top(self, labware_id: str) -> None:
|
|
449
|
+
"""Raise if labware has another labware on top."""
|
|
450
|
+
for labware in self._state.labware_by_id.values():
|
|
451
|
+
if (
|
|
452
|
+
isinstance(labware.location, OnLabwareLocation)
|
|
453
|
+
and labware.location.labwareId == labware_id
|
|
454
|
+
):
|
|
455
|
+
raise errors.LabwareIsInStackError(
|
|
456
|
+
f"Cannot access labware {labware_id} because it has another labware stacked on top."
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
def get_by_slot(
|
|
460
|
+
self,
|
|
461
|
+
slot_name: Union[DeckSlotName, StagingSlotName],
|
|
462
|
+
) -> Optional[LoadedLabware]:
|
|
463
|
+
"""Get the labware located in a given slot, if any."""
|
|
464
|
+
loaded_labware = list(self._state.labware_by_id.values())
|
|
465
|
+
|
|
466
|
+
for labware in loaded_labware:
|
|
467
|
+
if (
|
|
468
|
+
isinstance(labware.location, DeckSlotLocation)
|
|
469
|
+
and labware.location.slotName.id == slot_name.id
|
|
470
|
+
) or (
|
|
471
|
+
isinstance(labware.location, AddressableAreaLocation)
|
|
472
|
+
and labware.location.addressableAreaName == slot_name.id
|
|
473
|
+
):
|
|
474
|
+
return labware
|
|
475
|
+
|
|
476
|
+
return None
|
|
477
|
+
|
|
478
|
+
def get_by_addressable_area(
|
|
479
|
+
self,
|
|
480
|
+
addressable_area: str,
|
|
481
|
+
) -> Optional[LoadedLabware]:
|
|
482
|
+
"""Get the labware located in a given addressable area, if any."""
|
|
483
|
+
loaded_labware = list(self._state.labware_by_id.values())
|
|
484
|
+
|
|
485
|
+
for labware in loaded_labware:
|
|
486
|
+
if (
|
|
487
|
+
isinstance(labware.location, AddressableAreaLocation)
|
|
488
|
+
and labware.location.addressableAreaName == addressable_area
|
|
489
|
+
):
|
|
490
|
+
return labware
|
|
491
|
+
|
|
492
|
+
return None
|
|
493
|
+
|
|
494
|
+
def get_definition(self, labware_id: str) -> LabwareDefinition:
|
|
495
|
+
"""Get labware definition by the labware's unique identifier."""
|
|
496
|
+
return self.get_definition_by_uri(
|
|
497
|
+
LabwareUri(self.get(labware_id).definitionUri)
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
def get_user_specified_display_name(self, labware_id: str) -> Optional[str]:
|
|
501
|
+
"""Get the labware's user-specified display name, if set."""
|
|
502
|
+
return self.get(labware_id).displayName
|
|
503
|
+
|
|
504
|
+
def get_display_name(self, labware_id: str) -> str:
|
|
505
|
+
"""Get the labware's display name.
|
|
506
|
+
|
|
507
|
+
If a user-specified display name exists, will return that, else will return
|
|
508
|
+
display name from the definition.
|
|
509
|
+
"""
|
|
510
|
+
return (
|
|
511
|
+
self.get_user_specified_display_name(labware_id)
|
|
512
|
+
or self.get_definition(labware_id).metadata.displayName
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
def get_deck_definition(self) -> DeckDefinitionV5:
|
|
516
|
+
"""Get the current deck definition."""
|
|
517
|
+
return self._state.deck_definition
|
|
518
|
+
|
|
519
|
+
def get_definition_by_uri(self, uri: LabwareUri) -> LabwareDefinition:
|
|
520
|
+
"""Get the labware definition matching loadName namespace and version."""
|
|
521
|
+
try:
|
|
522
|
+
return self._state.definitions_by_uri[uri]
|
|
523
|
+
except KeyError as e:
|
|
524
|
+
raise errors.LabwareDefinitionDoesNotExistError(
|
|
525
|
+
f"Labware definition for matching {uri} not found."
|
|
526
|
+
) from e
|
|
527
|
+
|
|
528
|
+
def get_loaded_labware_definitions(self) -> List[LabwareDefinition]:
|
|
529
|
+
"""Get all loaded labware definitions."""
|
|
530
|
+
loaded_labware = self._state.labware_by_id.values()
|
|
531
|
+
return [
|
|
532
|
+
self.get_definition_by_uri(LabwareUri(labware.definitionUri))
|
|
533
|
+
for labware in loaded_labware
|
|
534
|
+
]
|
|
535
|
+
|
|
536
|
+
def find_custom_labware_load_params(self) -> List[LabwareLoadParams]:
|
|
537
|
+
"""Find all load labware parameters for custom labware definitions in state."""
|
|
538
|
+
return [
|
|
539
|
+
LabwareLoadParams(
|
|
540
|
+
load_name=definition.parameters.loadName,
|
|
541
|
+
namespace=definition.namespace,
|
|
542
|
+
version=definition.version,
|
|
543
|
+
)
|
|
544
|
+
for definition in self._state.definitions_by_uri.values()
|
|
545
|
+
if definition.namespace != OPENTRONS_NAMESPACE
|
|
546
|
+
]
|
|
547
|
+
|
|
548
|
+
def get_location(self, labware_id: str) -> LabwareLocation:
|
|
549
|
+
"""Get labware location by the labware's unique identifier."""
|
|
550
|
+
return self.get(labware_id).location
|
|
551
|
+
|
|
552
|
+
def get_parent_location(self, labware_id: str) -> NonStackedLocation:
|
|
553
|
+
"""Get labware's non-labware parent location."""
|
|
554
|
+
parent = self.get_location(labware_id)
|
|
555
|
+
if isinstance(parent, OnLabwareLocation):
|
|
556
|
+
return self.get_parent_location(parent.labwareId)
|
|
557
|
+
elif isinstance(parent, InStackerHopperLocation):
|
|
558
|
+
# TODO: This function really wants to return something like an "EventuallyOnDeckLocation"
|
|
559
|
+
# and either raise or return None for labware that isn't traceable to a place on the robot
|
|
560
|
+
# deck (i.e. not in a stacker hopper, not off-deck, not in system). We don't really have
|
|
561
|
+
# that concept yet but should add it soon. In the meantime, other checks should prevent
|
|
562
|
+
# this being called in those cases.
|
|
563
|
+
return ModuleLocation(moduleId=parent.moduleId)
|
|
564
|
+
return parent
|
|
565
|
+
|
|
566
|
+
def get_highest_child_labware(self, labware_id: str) -> str:
|
|
567
|
+
"""Get labware's highest child labware returning the labware ID."""
|
|
568
|
+
if (child_id := self.get_next_child_labware(labware_id)) is not None:
|
|
569
|
+
return self.get_highest_child_labware(labware_id=child_id)
|
|
570
|
+
return labware_id
|
|
571
|
+
|
|
572
|
+
def get_next_child_labware(self, labware_id: str) -> str | None:
|
|
573
|
+
"""Get the labware that is on this labware, if any.
|
|
574
|
+
|
|
575
|
+
This includes lids.
|
|
576
|
+
"""
|
|
577
|
+
for labware in self._state.labware_by_id.values():
|
|
578
|
+
if (
|
|
579
|
+
isinstance(labware.location, OnLabwareLocation)
|
|
580
|
+
and labware.location.labwareId == labware_id
|
|
581
|
+
):
|
|
582
|
+
return labware.id
|
|
583
|
+
return None
|
|
584
|
+
|
|
585
|
+
def get_labware_stack_from_parent(self, labware_id: str) -> list[str]:
|
|
586
|
+
"""Get the stack of labware starting from the specified labware ID and moving up."""
|
|
587
|
+
labware_ids = [labware_id]
|
|
588
|
+
while (next_id := self.get_next_child_labware(labware_id)) is not None:
|
|
589
|
+
labware_ids.append(next_id)
|
|
590
|
+
labware_id = next_id
|
|
591
|
+
return labware_ids
|
|
592
|
+
|
|
593
|
+
def get_labware_stack(
|
|
594
|
+
self, labware_stack: List[LoadedLabware]
|
|
595
|
+
) -> List[LoadedLabware]:
|
|
596
|
+
"""Get the a stack of labware starting from a given labware or existing stack."""
|
|
597
|
+
parent = self.get_location(labware_stack[-1].id)
|
|
598
|
+
if isinstance(parent, OnLabwareLocation):
|
|
599
|
+
labware_stack.append(self.get(parent.labwareId))
|
|
600
|
+
return self.get_labware_stack(labware_stack)
|
|
601
|
+
return labware_stack
|
|
602
|
+
|
|
603
|
+
def get_lid_id_by_labware_id(self, labware_id: str) -> str | None:
|
|
604
|
+
"""Get the ID of a lid labware on top of a given labware, if any."""
|
|
605
|
+
return self._state.labware_by_id[labware_id].lid_id
|
|
606
|
+
|
|
607
|
+
def get_lid_by_labware_id(self, labware_id: str) -> LoadedLabware | None:
|
|
608
|
+
"""Get the Lid Labware that is currently on top of a given labware, if there is one."""
|
|
609
|
+
lid_id = self.get_lid_id_by_labware_id(labware_id)
|
|
610
|
+
if lid_id:
|
|
611
|
+
return self._state.labware_by_id[lid_id]
|
|
612
|
+
else:
|
|
613
|
+
return None
|
|
614
|
+
|
|
615
|
+
def get_labware_by_lid_id(self, lid_id: str) -> LoadedLabware | None:
|
|
616
|
+
"""Get the labware that is currently covered by a given lid, if there is one."""
|
|
617
|
+
loaded_labware = list(self._state.labware_by_id.values())
|
|
618
|
+
for labware in loaded_labware:
|
|
619
|
+
if labware.lid_id == lid_id:
|
|
620
|
+
return labware
|
|
621
|
+
return None
|
|
622
|
+
|
|
623
|
+
def get_all(self) -> List[LoadedLabware]:
|
|
624
|
+
"""Get a list of all labware entries in state."""
|
|
625
|
+
return list(self._state.labware_by_id.values())
|
|
626
|
+
|
|
627
|
+
def get_has_quirk(self, labware_id: str, quirk: str) -> bool:
|
|
628
|
+
"""Get if a labware has a certain quirk."""
|
|
629
|
+
return quirk in self.get_quirks(labware_id=labware_id)
|
|
630
|
+
|
|
631
|
+
def get_quirks(self, labware_id: str) -> List[str]:
|
|
632
|
+
"""Get a labware's quirks."""
|
|
633
|
+
definition = self.get_definition(labware_id)
|
|
634
|
+
return definition.parameters.quirks or []
|
|
635
|
+
|
|
636
|
+
def get_should_center_column_on_target_well(self, labware_id: str) -> bool:
|
|
637
|
+
"""True if a pipette moving to this labware should center its active column on the target.
|
|
638
|
+
|
|
639
|
+
This is true for labware that have wells spanning entire columns.
|
|
640
|
+
"""
|
|
641
|
+
has_quirk = self.get_has_quirk(labware_id, "centerMultichannelOnWells")
|
|
642
|
+
return has_quirk and (
|
|
643
|
+
len(self.get_definition(labware_id).wells) > 1
|
|
644
|
+
and len(self.get_definition(labware_id).wells) < 96
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
def get_labware_stacking_maximum(self, labware: LabwareDefinition) -> int:
|
|
648
|
+
"""Returns the maximum number of labware allowed in a stack for a given labware definition.
|
|
649
|
+
|
|
650
|
+
If not defined within a labware, defaults to one.
|
|
651
|
+
"""
|
|
652
|
+
return labware.stackLimit if labware.stackLimit is not None else 1
|
|
653
|
+
|
|
654
|
+
def get_should_center_pipette_on_target_well(self, labware_id: str) -> bool:
|
|
655
|
+
"""True if a pipette moving to a well of this labware should center its body on the target.
|
|
656
|
+
|
|
657
|
+
This is true for 1-well reservoirs no matter the pipette, and for large plates.
|
|
658
|
+
"""
|
|
659
|
+
has_quirk = self.get_has_quirk(labware_id, "centerMultichannelOnWells")
|
|
660
|
+
return has_quirk and (
|
|
661
|
+
len(self.get_definition(labware_id).wells) == 1
|
|
662
|
+
or len(self.get_definition(labware_id).wells) >= 96
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
def get_well_definition(
|
|
666
|
+
self,
|
|
667
|
+
labware_id: str,
|
|
668
|
+
well_name: Optional[str] = None,
|
|
669
|
+
) -> WellDefinition2 | WellDefinition3:
|
|
670
|
+
"""Get a well's definition by labware and well name.
|
|
671
|
+
|
|
672
|
+
If `well_name` is omitted, the first well in the labware
|
|
673
|
+
will be used.
|
|
674
|
+
"""
|
|
675
|
+
definition = self.get_definition(labware_id)
|
|
676
|
+
if well_name is None:
|
|
677
|
+
well_name = definition.ordering[0][0]
|
|
678
|
+
|
|
679
|
+
try:
|
|
680
|
+
return definition.wells[well_name]
|
|
681
|
+
except KeyError as e:
|
|
682
|
+
raise errors.WellDoesNotExistError(
|
|
683
|
+
f"{well_name} does not exist in {labware_id}."
|
|
684
|
+
) from e
|
|
685
|
+
|
|
686
|
+
def get_well_geometry(
|
|
687
|
+
self, labware_id: str, well_name: Optional[str] = None
|
|
688
|
+
) -> InnerWellGeometry | UserDefinedVolumes:
|
|
689
|
+
"""Get a well's inner geometry by labware and well name."""
|
|
690
|
+
labware_def = self.get_definition(labware_id)
|
|
691
|
+
if labware_def.innerLabwareGeometry is None:
|
|
692
|
+
raise errors.IncompleteLabwareDefinitionError(
|
|
693
|
+
message=f"No innerLabwareGeometry found in labware definition for labware_id: {labware_id}."
|
|
694
|
+
)
|
|
695
|
+
well_def = self.get_well_definition(labware_id, well_name)
|
|
696
|
+
geometry_id = well_def.geometryDefinitionId
|
|
697
|
+
if geometry_id is None:
|
|
698
|
+
raise errors.IncompleteWellDefinitionError(
|
|
699
|
+
message=f"No geometryDefinitionId found in well definition for well: {well_name} in labware_id: {labware_id}"
|
|
700
|
+
)
|
|
701
|
+
else:
|
|
702
|
+
well_geometry = labware_def.innerLabwareGeometry.get(geometry_id)
|
|
703
|
+
if well_geometry is None:
|
|
704
|
+
raise errors.IncompleteLabwareDefinitionError(
|
|
705
|
+
message=f"No innerLabwareGeometry found in labware definition for well_id: {geometry_id} in labware_id: {labware_id}"
|
|
706
|
+
)
|
|
707
|
+
return well_geometry
|
|
708
|
+
|
|
709
|
+
def get_well_size(
|
|
710
|
+
self, labware_id: str, well_name: str
|
|
711
|
+
) -> Tuple[float, float, float]:
|
|
712
|
+
"""Get a well's size in x, y, z dimensions based on its shape.
|
|
713
|
+
|
|
714
|
+
Args:
|
|
715
|
+
labware_id: Labware identifier.
|
|
716
|
+
well_name: Name of well in labware.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
A tuple of dimensions in x, y, and z. If well is circular,
|
|
720
|
+
the x and y dimensions will both be set to the diameter.
|
|
721
|
+
"""
|
|
722
|
+
well_definition = self.get_well_definition(labware_id, well_name)
|
|
723
|
+
|
|
724
|
+
if well_definition.shape == "circular":
|
|
725
|
+
x_size = y_size = well_definition.diameter
|
|
726
|
+
elif well_definition.shape == "rectangular":
|
|
727
|
+
x_size = well_definition.xDimension
|
|
728
|
+
y_size = well_definition.yDimension
|
|
729
|
+
else:
|
|
730
|
+
assert_never(well_definition.shape)
|
|
731
|
+
|
|
732
|
+
return x_size, y_size, well_definition.depth
|
|
733
|
+
|
|
734
|
+
def get_well_radial_offsets(
|
|
735
|
+
self, labware_id: str, well_name: str, radius_percentage: float
|
|
736
|
+
) -> Tuple[float, float]:
|
|
737
|
+
"""Get x and y radius offsets modified by radius percentage."""
|
|
738
|
+
x_size, y_size, z_size = self.get_well_size(labware_id, well_name)
|
|
739
|
+
return (x_size / 2.0) * radius_percentage, (y_size / 2.0) * radius_percentage
|
|
740
|
+
|
|
741
|
+
def get_edge_path_type(
|
|
742
|
+
self,
|
|
743
|
+
labware_id: str,
|
|
744
|
+
well_name: str,
|
|
745
|
+
mount: MountType,
|
|
746
|
+
labware_slot: DeckSlotName,
|
|
747
|
+
next_to_module: bool,
|
|
748
|
+
) -> EdgePathType:
|
|
749
|
+
"""Get the recommended edge path type based on well column, labware position and any neighboring modules."""
|
|
750
|
+
labware_definition = self.get_definition(labware_id)
|
|
751
|
+
left_column = labware_definition.ordering[0]
|
|
752
|
+
right_column = labware_definition.ordering[-1]
|
|
753
|
+
|
|
754
|
+
left_path_criteria = mount is MountType.RIGHT and well_name in left_column
|
|
755
|
+
right_path_criteria = mount is MountType.LEFT and well_name in right_column
|
|
756
|
+
labware_right_side = labware_slot in _RIGHT_SIDE_SLOTS
|
|
757
|
+
|
|
758
|
+
if left_path_criteria and (next_to_module or labware_right_side):
|
|
759
|
+
return EdgePathType.LEFT
|
|
760
|
+
elif right_path_criteria and next_to_module:
|
|
761
|
+
return EdgePathType.RIGHT
|
|
762
|
+
else:
|
|
763
|
+
return EdgePathType.DEFAULT
|
|
764
|
+
|
|
765
|
+
def validate_liquid_allowed_in_labware(
|
|
766
|
+
self, labware_id: str, wells: Mapping[str, Any]
|
|
767
|
+
) -> List[str]:
|
|
768
|
+
"""Check if wells associated to a labware_id has well by name and that labware is not tiprack."""
|
|
769
|
+
labware_definition = self.get_definition(labware_id)
|
|
770
|
+
labware_wells = labware_definition.wells
|
|
771
|
+
contains_wells = all(well_name in labware_wells for well_name in iter(wells))
|
|
772
|
+
if labware_definition.parameters.isTiprack:
|
|
773
|
+
raise errors.LabwareIsTipRackError(
|
|
774
|
+
f"Given labware: {labware_id} is a tiprack. Can not load liquid."
|
|
775
|
+
)
|
|
776
|
+
if LabwareRole.adapter in labware_definition.allowedRoles:
|
|
777
|
+
raise errors.LabwareIsAdapterError(
|
|
778
|
+
f"Given labware: {labware_id} is an adapter. Can not load liquid."
|
|
779
|
+
)
|
|
780
|
+
if not contains_wells:
|
|
781
|
+
raise errors.WellDoesNotExistError(
|
|
782
|
+
f"Some of the supplied wells do not match the labwareId: {labware_id}."
|
|
783
|
+
)
|
|
784
|
+
return list(wells)
|
|
785
|
+
|
|
786
|
+
def get_tip_length(self, labware_id: str, overlap: float = 0) -> float:
|
|
787
|
+
"""Get the nominal tip length of a tip rack."""
|
|
788
|
+
definition = self.get_definition(labware_id)
|
|
789
|
+
if definition.parameters.tipLength is None:
|
|
790
|
+
raise errors.LabwareIsNotTipRackError(
|
|
791
|
+
f"Labware {labware_id} has no tip length defined."
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
return definition.parameters.tipLength - overlap
|
|
795
|
+
|
|
796
|
+
def get_tip_drop_z_offset(
|
|
797
|
+
self, labware_id: str, length_scale: float, additional_offset: float
|
|
798
|
+
) -> float:
|
|
799
|
+
"""Get the tip drop offset from the top of the well."""
|
|
800
|
+
tip_length = self.get_tip_length(labware_id)
|
|
801
|
+
return -tip_length * length_scale + additional_offset
|
|
802
|
+
|
|
803
|
+
def get_definition_uri(self, labware_id: str) -> LabwareUri:
|
|
804
|
+
"""Get a labware's definition URI."""
|
|
805
|
+
return LabwareUri(self.get(labware_id).definitionUri)
|
|
806
|
+
|
|
807
|
+
def get_uri_from_definition(
|
|
808
|
+
self,
|
|
809
|
+
labware_definition: LabwareDefinition,
|
|
810
|
+
) -> LabwareUri:
|
|
811
|
+
"""Get a definition URI from a full labware definition."""
|
|
812
|
+
return uri_from_details(
|
|
813
|
+
load_name=labware_definition.parameters.loadName,
|
|
814
|
+
namespace=labware_definition.namespace,
|
|
815
|
+
version=labware_definition.version,
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
@overload
|
|
819
|
+
def get_uri_from_definition_unless_none(
|
|
820
|
+
self, labware_definition: LabwareDefinition
|
|
821
|
+
) -> str:
|
|
822
|
+
...
|
|
823
|
+
|
|
824
|
+
@overload
|
|
825
|
+
def get_uri_from_definition_unless_none(self, labware_definition: None) -> None:
|
|
826
|
+
...
|
|
827
|
+
|
|
828
|
+
def get_uri_from_definition_unless_none(
|
|
829
|
+
self, labware_definition: LabwareDefinition | None
|
|
830
|
+
) -> str | None:
|
|
831
|
+
"""Get the URI from a labware definition, passing None through.
|
|
832
|
+
|
|
833
|
+
Don't use unless you're sure you want to accept that the definition might be None.
|
|
834
|
+
"""
|
|
835
|
+
if labware_definition is None:
|
|
836
|
+
return None
|
|
837
|
+
return self.get_uri_from_definition(labware_definition)
|
|
838
|
+
|
|
839
|
+
def is_tiprack(self, labware_id: str) -> bool:
|
|
840
|
+
"""Get whether labware is a tiprack."""
|
|
841
|
+
definition = self.get_definition(labware_id)
|
|
842
|
+
return definition.parameters.isTiprack
|
|
843
|
+
|
|
844
|
+
def get_load_name(self, labware_id: str) -> str:
|
|
845
|
+
"""Get the labware's load name."""
|
|
846
|
+
definition = self.get_definition(labware_id)
|
|
847
|
+
return definition.parameters.loadName
|
|
848
|
+
|
|
849
|
+
@overload
|
|
850
|
+
def get_dimensions(self, *, labware_definition: LabwareDefinition) -> Dimensions:
|
|
851
|
+
pass
|
|
852
|
+
|
|
853
|
+
@overload
|
|
854
|
+
def get_dimensions(self, *, labware_id: str) -> Dimensions:
|
|
855
|
+
pass
|
|
856
|
+
|
|
857
|
+
def get_dimensions(
|
|
858
|
+
self,
|
|
859
|
+
*,
|
|
860
|
+
labware_definition: LabwareDefinition | None = None,
|
|
861
|
+
labware_id: str | None = None,
|
|
862
|
+
) -> Dimensions:
|
|
863
|
+
"""Get the labware's dimensions."""
|
|
864
|
+
if labware_definition is None:
|
|
865
|
+
assert labware_id is not None # From our @overloads.
|
|
866
|
+
labware_definition = self.get_definition(labware_id)
|
|
867
|
+
|
|
868
|
+
extents = self.get_extents_around_lw_origin(labware_definition)
|
|
869
|
+
return Dimensions(
|
|
870
|
+
x=extents.x_dimension, y=extents.y_dimension, z=extents.z_dimension
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
def get_extents_around_lw_origin(
|
|
874
|
+
self,
|
|
875
|
+
labware_definition: LabwareDefinition,
|
|
876
|
+
) -> AxisAlignedBoundingBox3D:
|
|
877
|
+
"""Return a bounding box around all the space the labware occupies, all-encompassing.
|
|
878
|
+
|
|
879
|
+
Returned coordinates are relative to the labware's local origin.
|
|
880
|
+
"""
|
|
881
|
+
if labware_definition.schemaVersion == 2:
|
|
882
|
+
x_dimension = labware_definition.dimensions.xDimension
|
|
883
|
+
y_dimension = labware_definition.dimensions.yDimension
|
|
884
|
+
z_dimension = labware_definition.dimensions.zDimension
|
|
885
|
+
return AxisAlignedBoundingBox3D.from_corners(
|
|
886
|
+
Point(0, 0, 0), Point(x_dimension, y_dimension, z_dimension)
|
|
887
|
+
)
|
|
888
|
+
else:
|
|
889
|
+
return AxisAlignedBoundingBox3D.from_corners(
|
|
890
|
+
Point.from_xyz_attrs(labware_definition.extents.total.backLeftBottom),
|
|
891
|
+
Point.from_xyz_attrs(labware_definition.extents.total.frontRightTop),
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
def get_labware_overlap_offsets(
|
|
895
|
+
self, definition: LabwareDefinition, below_labware_name: str
|
|
896
|
+
) -> OverlapOffset:
|
|
897
|
+
"""Get the labware's overlap with requested labware's load name."""
|
|
898
|
+
if below_labware_name in definition.stackingOffsetWithLabware.keys():
|
|
899
|
+
stacking_overlap = definition.stackingOffsetWithLabware.get(
|
|
900
|
+
below_labware_name, OverlapOffset(x=0, y=0, z=0)
|
|
901
|
+
)
|
|
902
|
+
else:
|
|
903
|
+
stacking_overlap = definition.stackingOffsetWithLabware.get(
|
|
904
|
+
"default", OverlapOffset(x=0, y=0, z=0)
|
|
905
|
+
)
|
|
906
|
+
return OverlapOffset(
|
|
907
|
+
x=stacking_overlap.x, y=stacking_overlap.y, z=stacking_overlap.z
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
def get_default_magnet_height(self, module_id: str, offset: float) -> float:
|
|
911
|
+
"""Return a labware's default Magnetic Module engage height with added offset, if supplied.
|
|
912
|
+
|
|
913
|
+
The returned value is measured in millimeters above the labware base plane.
|
|
914
|
+
"""
|
|
915
|
+
labware_id = self.get_id_by_module(module_id)
|
|
916
|
+
parameters = self.get_definition(labware_id).parameters
|
|
917
|
+
default_engage_height = parameters.magneticModuleEngageHeight
|
|
918
|
+
if (
|
|
919
|
+
parameters.isMagneticModuleCompatible is False
|
|
920
|
+
or default_engage_height is None
|
|
921
|
+
):
|
|
922
|
+
raise errors.exceptions.NoMagnetEngageHeightError(
|
|
923
|
+
"The labware loaded on this Magnetic Module"
|
|
924
|
+
" does not have a default engage height."
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
if self._is_magnetic_module_uri_in_half_millimeter(labware_id):
|
|
928
|
+
# TODO(mc, 2022-09-26): this value likely _also_ needs a few mm subtracted
|
|
929
|
+
# https://opentrons.atlassian.net/browse/RSS-111
|
|
930
|
+
calculated_height = default_engage_height / 2.0
|
|
931
|
+
else:
|
|
932
|
+
calculated_height = default_engage_height
|
|
933
|
+
|
|
934
|
+
return calculated_height + offset
|
|
935
|
+
|
|
936
|
+
def get_labware_offset_vector(self, labware_id: str) -> LabwareOffsetVector:
|
|
937
|
+
"""Get the labware's calibration offset."""
|
|
938
|
+
offset_id = self.get(labware_id=labware_id).offsetId
|
|
939
|
+
if offset_id is None:
|
|
940
|
+
return LabwareOffsetVector(x=0, y=0, z=0)
|
|
941
|
+
else:
|
|
942
|
+
return self._state.labware_offsets_by_id[offset_id].vector
|
|
943
|
+
|
|
944
|
+
def get_labware_offset(self, labware_offset_id: str) -> LabwareOffset:
|
|
945
|
+
"""Get a labware offset by the offset's unique ID.
|
|
946
|
+
|
|
947
|
+
Raises:
|
|
948
|
+
LabwareOffsetDoesNotExistError: If the given ID does not match any
|
|
949
|
+
previously added offset.
|
|
950
|
+
"""
|
|
951
|
+
try:
|
|
952
|
+
return self._state.labware_offsets_by_id[labware_offset_id]
|
|
953
|
+
except KeyError as e:
|
|
954
|
+
raise errors.LabwareOffsetDoesNotExistError(
|
|
955
|
+
f"Labware offset {labware_offset_id} not found."
|
|
956
|
+
) from e
|
|
957
|
+
|
|
958
|
+
def get_labware_offsets(self) -> List[LabwareOffset]:
|
|
959
|
+
"""Get all labware offsets, in the order they were added."""
|
|
960
|
+
return list(self._state.labware_offsets_by_id.values())
|
|
961
|
+
|
|
962
|
+
def find_applicable_labware_offset(
|
|
963
|
+
self, definition_uri: str, location: LabwareOffsetLocationSequence
|
|
964
|
+
) -> Optional[LabwareOffset]:
|
|
965
|
+
"""Find a labware offset that applies to the given definition and location sequence.
|
|
966
|
+
|
|
967
|
+
Returns the *most recently* added matching offset, so later ones can override earlier ones.
|
|
968
|
+
Returns ``None`` if no loaded offset matches the location.
|
|
969
|
+
|
|
970
|
+
An offset matches a labware instance if the sequence of locations formed by following the
|
|
971
|
+
.location elements of the labware instance until you reach an addressable area has the same
|
|
972
|
+
definition URIs as the sequence of definition URIs stored by the offset.
|
|
973
|
+
"""
|
|
974
|
+
for candidate in reversed(list(self._state.labware_offsets_by_id.values())):
|
|
975
|
+
if (
|
|
976
|
+
candidate.definitionUri == definition_uri
|
|
977
|
+
and candidate.locationSequence == location
|
|
978
|
+
):
|
|
979
|
+
return candidate
|
|
980
|
+
return None
|
|
981
|
+
|
|
982
|
+
def find_applicable_labware_offset_by_legacy_location(
|
|
983
|
+
self,
|
|
984
|
+
definition_uri: str,
|
|
985
|
+
location: LegacyLabwareOffsetLocation,
|
|
986
|
+
) -> Optional[LabwareOffset]:
|
|
987
|
+
"""Find a labware offset that applies to the given definition and legacy location.
|
|
988
|
+
|
|
989
|
+
Returns the *most recently* added matching offset,
|
|
990
|
+
so later offsets can override earlier ones.
|
|
991
|
+
Or, ``None`` if no offsets match at all.
|
|
992
|
+
|
|
993
|
+
An offset "matches"
|
|
994
|
+
if its ``definition_uri`` and ``location`` *exactly* match what's provided.
|
|
995
|
+
This implies that if the location involves a module,
|
|
996
|
+
it will *not* match a module that's compatible but not identical.
|
|
997
|
+
"""
|
|
998
|
+
for candidate in reversed(list(self._state.labware_offsets_by_id.values())):
|
|
999
|
+
if (
|
|
1000
|
+
candidate.definitionUri == definition_uri
|
|
1001
|
+
and candidate.location == location
|
|
1002
|
+
):
|
|
1003
|
+
return candidate
|
|
1004
|
+
|
|
1005
|
+
return None
|
|
1006
|
+
|
|
1007
|
+
def get_fixed_trash_id(self) -> Optional[str]:
|
|
1008
|
+
"""Get the identifier of labware loaded into the fixed trash location.
|
|
1009
|
+
|
|
1010
|
+
Raises:
|
|
1011
|
+
LabwareNotLoadedError: a fixed trash was not loaded by the deck definition
|
|
1012
|
+
that is currently in use for the protocol run.
|
|
1013
|
+
"""
|
|
1014
|
+
for labware in self._state.labware_by_id.values():
|
|
1015
|
+
if isinstance(
|
|
1016
|
+
labware.location, DeckSlotLocation
|
|
1017
|
+
) and labware.location.slotName in {
|
|
1018
|
+
DeckSlotName.FIXED_TRASH,
|
|
1019
|
+
DeckSlotName.SLOT_A3,
|
|
1020
|
+
}:
|
|
1021
|
+
return labware.id
|
|
1022
|
+
return None
|
|
1023
|
+
|
|
1024
|
+
def is_fixed_trash(self, labware_id: str) -> bool:
|
|
1025
|
+
"""Check if labware is fixed trash."""
|
|
1026
|
+
return self.get_has_quirk(labware_id, "fixedTrash")
|
|
1027
|
+
|
|
1028
|
+
def is_absorbance_reader_lid(self, labware_id: str) -> bool:
|
|
1029
|
+
"""Check if labware is an absorbance reader lid."""
|
|
1030
|
+
return labware_validation.is_absorbance_reader_lid(
|
|
1031
|
+
self.get(labware_id).loadName
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
def is_lid(self, labware_id: str) -> bool:
|
|
1035
|
+
"""Check if labware is a lid."""
|
|
1036
|
+
return LabwareRole.lid in self.get_definition(labware_id).allowedRoles
|
|
1037
|
+
|
|
1038
|
+
def raise_if_labware_in_location(
|
|
1039
|
+
self,
|
|
1040
|
+
location: OnDeckLabwareLocation,
|
|
1041
|
+
) -> None:
|
|
1042
|
+
"""Raise an error if the specified location has labware in it."""
|
|
1043
|
+
for labware in self.get_all():
|
|
1044
|
+
if labware.location == location:
|
|
1045
|
+
raise errors.LocationIsOccupiedError(
|
|
1046
|
+
f"Labware {labware.loadName} is already present at {location}."
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
def raise_if_labware_cannot_be_ondeck(
|
|
1050
|
+
self,
|
|
1051
|
+
location: LabwareLocation,
|
|
1052
|
+
labware_definition: LabwareDefinition,
|
|
1053
|
+
) -> None:
|
|
1054
|
+
"""Raise an error if the labware cannot be in the specified location."""
|
|
1055
|
+
if isinstance(
|
|
1056
|
+
location, (DeckSlotLocation, AddressableAreaLocation)
|
|
1057
|
+
) and not labware_validation.validate_labware_can_be_ondeck(labware_definition):
|
|
1058
|
+
raise errors.LabwareCannotSitOnDeckError(
|
|
1059
|
+
f"{labware_definition.parameters.loadName} cannot sit in a slot by itself."
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
def raise_if_labware_incompatible_with_plate_reader(
|
|
1063
|
+
self,
|
|
1064
|
+
labware_definition: LabwareDefinition,
|
|
1065
|
+
) -> None:
|
|
1066
|
+
"""Raise an error if the labware is not compatible with the plate reader."""
|
|
1067
|
+
load_name = labware_definition.parameters.loadName
|
|
1068
|
+
number_of_wells = len(labware_definition.wells)
|
|
1069
|
+
if number_of_wells != 96:
|
|
1070
|
+
raise errors.LabwareMovementNotAllowedError(
|
|
1071
|
+
f"Cannot move '{load_name}' into plate reader because the"
|
|
1072
|
+
f" labware contains {number_of_wells} wells where 96 wells is expected."
|
|
1073
|
+
)
|
|
1074
|
+
elif (
|
|
1075
|
+
self.get_dimensions(labware_definition=labware_definition).z
|
|
1076
|
+
> _PLATE_READER_MAX_LABWARE_Z_MM
|
|
1077
|
+
):
|
|
1078
|
+
raise errors.LabwareMovementNotAllowedError(
|
|
1079
|
+
f"Cannot move '{load_name}' into plate reader because the"
|
|
1080
|
+
f" maximum allowed labware height is {_PLATE_READER_MAX_LABWARE_Z_MM}mm."
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
def raise_if_stacker_labware_pool_is_not_valid(
|
|
1084
|
+
self,
|
|
1085
|
+
primary_labware_definition: LabwareDefinition,
|
|
1086
|
+
lid_labware_definition: LabwareDefinition | None,
|
|
1087
|
+
adapter_labware_definition: LabwareDefinition | None,
|
|
1088
|
+
) -> None:
|
|
1089
|
+
"""Raise if the primary, lid, and adapter do not go together."""
|
|
1090
|
+
if lid_labware_definition:
|
|
1091
|
+
if not labware_validation.validate_definition_is_lid(
|
|
1092
|
+
lid_labware_definition
|
|
1093
|
+
):
|
|
1094
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1095
|
+
f"Labware {lid_labware_definition.parameters.loadName} cannot be used as a lid in the Flex Stacker."
|
|
1096
|
+
)
|
|
1097
|
+
if not labware_validation.validate_labware_can_be_stacked(
|
|
1098
|
+
lid_labware_definition, primary_labware_definition.parameters.loadName
|
|
1099
|
+
):
|
|
1100
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1101
|
+
f"Labware {lid_labware_definition.parameters.loadName} cannot be used as a lid for {primary_labware_definition.parameters.loadName}"
|
|
1102
|
+
)
|
|
1103
|
+
if adapter_labware_definition:
|
|
1104
|
+
if not labware_validation.validate_definition_is_adapter(
|
|
1105
|
+
adapter_labware_definition
|
|
1106
|
+
):
|
|
1107
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1108
|
+
f"Labware {adapter_labware_definition.parameters.loadName} cannot be used as an adapter in the Flex Stacker."
|
|
1109
|
+
)
|
|
1110
|
+
if not labware_validation.validate_labware_can_be_stacked(
|
|
1111
|
+
primary_labware_definition,
|
|
1112
|
+
adapter_labware_definition.parameters.loadName,
|
|
1113
|
+
):
|
|
1114
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1115
|
+
f"Labware {adapter_labware_definition.parameters.loadName} cannot be used as an adapter for {primary_labware_definition.parameters.loadName}"
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
def stacker_labware_pool_to_ordered_list(
|
|
1119
|
+
self,
|
|
1120
|
+
primary_labware_definition: LabwareDefinition,
|
|
1121
|
+
lid_labware_definition: LabwareDefinition | None,
|
|
1122
|
+
adapter_labware_definition: LabwareDefinition | None,
|
|
1123
|
+
) -> List[LabwareDefinition]:
|
|
1124
|
+
"""Get the pool definitions in the top-first order suitable for geometry calculations."""
|
|
1125
|
+
self.raise_if_stacker_labware_pool_is_not_valid(
|
|
1126
|
+
primary_labware_definition,
|
|
1127
|
+
lid_labware_definition,
|
|
1128
|
+
adapter_labware_definition,
|
|
1129
|
+
)
|
|
1130
|
+
return [
|
|
1131
|
+
x
|
|
1132
|
+
for x in [
|
|
1133
|
+
lid_labware_definition,
|
|
1134
|
+
primary_labware_definition,
|
|
1135
|
+
adapter_labware_definition,
|
|
1136
|
+
]
|
|
1137
|
+
if x is not None
|
|
1138
|
+
]
|
|
1139
|
+
|
|
1140
|
+
def get_stacker_labware_overlap_offset(
|
|
1141
|
+
self, definitions: list[LabwareDefinition]
|
|
1142
|
+
) -> OverlapOffset:
|
|
1143
|
+
"""Get the overlap amount between each labware pool.
|
|
1144
|
+
|
|
1145
|
+
The definitions must be in top-first order, ideally created by
|
|
1146
|
+
`stacker_labware_pool_to_ordered_list`.
|
|
1147
|
+
"""
|
|
1148
|
+
return self.get_labware_overlap_offsets(
|
|
1149
|
+
definitions[-1], definitions[0].parameters.loadName
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
def raise_if_labware_cannot_be_stacked( # noqa: C901
|
|
1153
|
+
self, top_labware_definition: LabwareDefinition, bottom_labware_id: str
|
|
1154
|
+
) -> None:
|
|
1155
|
+
"""Raise if the specified labware definition cannot be placed on top of the bottom labware."""
|
|
1156
|
+
if labware_validation.validate_definition_is_adapter(top_labware_definition):
|
|
1157
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1158
|
+
f"Labware {top_labware_definition.parameters.loadName} is defined as an adapter and cannot be placed"
|
|
1159
|
+
" on other labware."
|
|
1160
|
+
)
|
|
1161
|
+
below_labware = self.get(bottom_labware_id)
|
|
1162
|
+
if isinstance(
|
|
1163
|
+
top_labware_definition, LabwareDefinition2
|
|
1164
|
+
) and not labware_validation.validate_labware_can_be_stacked(
|
|
1165
|
+
top_labware_definition=top_labware_definition,
|
|
1166
|
+
below_labware_load_name=below_labware.loadName,
|
|
1167
|
+
):
|
|
1168
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1169
|
+
f"Labware {top_labware_definition.parameters.loadName} cannot be loaded onto labware {below_labware.loadName}"
|
|
1170
|
+
)
|
|
1171
|
+
elif (
|
|
1172
|
+
labware_validation.validate_definition_is_lid(top_labware_definition)
|
|
1173
|
+
and top_labware_definition.compatibleParentLabware is not None
|
|
1174
|
+
and self.get_load_name(bottom_labware_id)
|
|
1175
|
+
not in top_labware_definition.compatibleParentLabware
|
|
1176
|
+
):
|
|
1177
|
+
# This parent is assumed to be compatible, unless the lid enumerates
|
|
1178
|
+
# all its compatible parents and this parent is missing from the list.
|
|
1179
|
+
raise ValueError(
|
|
1180
|
+
f"Labware Lid {top_labware_definition.parameters.loadName} may not be loaded on parent labware"
|
|
1181
|
+
f" {self.get_display_name(bottom_labware_id)}."
|
|
1182
|
+
)
|
|
1183
|
+
elif isinstance(below_labware.location, ModuleLocation):
|
|
1184
|
+
below_definition = self.get_definition(labware_id=below_labware.id)
|
|
1185
|
+
if not labware_validation.validate_definition_is_adapter(
|
|
1186
|
+
below_definition
|
|
1187
|
+
) and not labware_validation.validate_definition_is_lid(
|
|
1188
|
+
top_labware_definition
|
|
1189
|
+
):
|
|
1190
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1191
|
+
f"Labware {top_labware_definition.parameters.loadName} cannot be loaded"
|
|
1192
|
+
f" onto a labware on top of a module"
|
|
1193
|
+
)
|
|
1194
|
+
elif isinstance(below_labware.location, OnLabwareLocation):
|
|
1195
|
+
labware_stack = self.get_labware_stack([below_labware])
|
|
1196
|
+
stack_without_adapters = []
|
|
1197
|
+
for lw in labware_stack:
|
|
1198
|
+
if not labware_validation.validate_definition_is_adapter(
|
|
1199
|
+
self.get_definition(lw.id)
|
|
1200
|
+
) and not labware_validation.is_lid_stack(self.get_load_name(lw.id)):
|
|
1201
|
+
stack_without_adapters.append(lw)
|
|
1202
|
+
if len(stack_without_adapters) >= self.get_labware_stacking_maximum(
|
|
1203
|
+
top_labware_definition
|
|
1204
|
+
):
|
|
1205
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1206
|
+
f"Labware {top_labware_definition.parameters.loadName} cannot be loaded to stack of more than {self.get_labware_stacking_maximum(top_labware_definition)} labware."
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
further_below_definition = self.get_definition(
|
|
1210
|
+
labware_id=below_labware.location.labwareId
|
|
1211
|
+
)
|
|
1212
|
+
if labware_validation.validate_definition_is_adapter(
|
|
1213
|
+
further_below_definition
|
|
1214
|
+
) and not labware_validation.validate_definition_is_lid(
|
|
1215
|
+
top_labware_definition
|
|
1216
|
+
):
|
|
1217
|
+
raise errors.LabwareCannotBeStackedError(
|
|
1218
|
+
f"Labware {top_labware_definition.parameters.loadName} cannot be loaded"
|
|
1219
|
+
f" onto labware on top of adapter"
|
|
1220
|
+
)
|
|
1221
|
+
|
|
1222
|
+
def _is_magnetic_module_uri_in_half_millimeter(self, labware_id: str) -> bool:
|
|
1223
|
+
"""Check whether the labware uri needs to be calculated in half a millimeter."""
|
|
1224
|
+
uri = self.get_uri_from_definition(self.get_definition(labware_id))
|
|
1225
|
+
return uri in _MAGDECK_HALF_MM_LABWARE
|
|
1226
|
+
|
|
1227
|
+
def get_deck_default_gripper_offsets(self) -> Optional[LabwareMovementOffsetData]:
|
|
1228
|
+
"""Get the deck's default gripper offsets."""
|
|
1229
|
+
parsed_offsets = (
|
|
1230
|
+
self.get_deck_definition().get("gripperOffsets", {}).get("default")
|
|
1231
|
+
)
|
|
1232
|
+
return (
|
|
1233
|
+
LabwareMovementOffsetData(
|
|
1234
|
+
pickUpOffset=LabwareOffsetVector(
|
|
1235
|
+
x=parsed_offsets["pickUpOffset"]["x"],
|
|
1236
|
+
y=parsed_offsets["pickUpOffset"]["y"],
|
|
1237
|
+
z=parsed_offsets["pickUpOffset"]["z"],
|
|
1238
|
+
),
|
|
1239
|
+
dropOffset=LabwareOffsetVector(
|
|
1240
|
+
x=parsed_offsets["dropOffset"]["x"],
|
|
1241
|
+
y=parsed_offsets["dropOffset"]["y"],
|
|
1242
|
+
z=parsed_offsets["dropOffset"]["z"],
|
|
1243
|
+
),
|
|
1244
|
+
)
|
|
1245
|
+
if parsed_offsets
|
|
1246
|
+
else None
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
def get_absorbance_reader_lid_definition(self) -> LabwareDefinition:
|
|
1250
|
+
"""Return the special labware definition for the plate reader lid.
|
|
1251
|
+
|
|
1252
|
+
See todo comments in `create_protocol_engine().
|
|
1253
|
+
"""
|
|
1254
|
+
# NOTE: This needs to stay in sync with create_protocol_engine().
|
|
1255
|
+
return self._state.definitions_by_uri[
|
|
1256
|
+
"opentrons/opentrons_flex_lid_absorbance_plate_reader_module/1"
|
|
1257
|
+
]
|
|
1258
|
+
|
|
1259
|
+
@overload
|
|
1260
|
+
def get_child_gripper_offsets(
|
|
1261
|
+
self,
|
|
1262
|
+
*,
|
|
1263
|
+
labware_definition: LabwareDefinition,
|
|
1264
|
+
slot_name: Optional[DeckSlotName],
|
|
1265
|
+
) -> Optional[LabwareMovementOffsetData]:
|
|
1266
|
+
pass
|
|
1267
|
+
|
|
1268
|
+
@overload
|
|
1269
|
+
def get_child_gripper_offsets(
|
|
1270
|
+
self, *, labware_id: str, slot_name: Optional[DeckSlotName]
|
|
1271
|
+
) -> Optional[LabwareMovementOffsetData]:
|
|
1272
|
+
pass
|
|
1273
|
+
|
|
1274
|
+
def get_child_gripper_offsets(
|
|
1275
|
+
self,
|
|
1276
|
+
*,
|
|
1277
|
+
labware_definition: Optional[LabwareDefinition] = None,
|
|
1278
|
+
labware_id: Optional[str] = None,
|
|
1279
|
+
slot_name: Optional[DeckSlotName],
|
|
1280
|
+
) -> Optional[LabwareMovementOffsetData]:
|
|
1281
|
+
"""Get the grip offsets that a labware says should be applied to children stacked atop it.
|
|
1282
|
+
|
|
1283
|
+
Params:
|
|
1284
|
+
labware_id: The ID of a parent labware (atop which another labware, the child, will be stacked).
|
|
1285
|
+
slot_name: The ancestor slot that the parent labware is ultimately loaded into,
|
|
1286
|
+
perhaps after going through a module in the middle.
|
|
1287
|
+
|
|
1288
|
+
Returns:
|
|
1289
|
+
If `slot_name` is provided, returns the gripper offsets that the parent labware definition
|
|
1290
|
+
specifies just for that slot, or `None` if the labware definition doesn't have an
|
|
1291
|
+
exact match.
|
|
1292
|
+
|
|
1293
|
+
If `slot_name` is `None`, returns the gripper offsets that the parent labware
|
|
1294
|
+
definition designates as "default," or `None` if it doesn't designate any as such.
|
|
1295
|
+
"""
|
|
1296
|
+
if labware_id is not None:
|
|
1297
|
+
labware_definition = self.get_definition(labware_id)
|
|
1298
|
+
else:
|
|
1299
|
+
# Should be ensured by our @overloads.
|
|
1300
|
+
assert labware_definition is not None
|
|
1301
|
+
|
|
1302
|
+
parsed_offsets = labware_definition.gripperOffsets
|
|
1303
|
+
offset_key = slot_name.id if slot_name else "default"
|
|
1304
|
+
|
|
1305
|
+
if parsed_offsets is None or offset_key not in parsed_offsets:
|
|
1306
|
+
return None
|
|
1307
|
+
else:
|
|
1308
|
+
return LabwareMovementOffsetData(
|
|
1309
|
+
pickUpOffset=LabwareOffsetVector.model_construct(
|
|
1310
|
+
x=parsed_offsets[offset_key].pickUpOffset.x,
|
|
1311
|
+
y=parsed_offsets[offset_key].pickUpOffset.y,
|
|
1312
|
+
z=parsed_offsets[offset_key].pickUpOffset.z,
|
|
1313
|
+
),
|
|
1314
|
+
dropOffset=LabwareOffsetVector.model_construct(
|
|
1315
|
+
x=parsed_offsets[offset_key].dropOffset.x,
|
|
1316
|
+
y=parsed_offsets[offset_key].dropOffset.y,
|
|
1317
|
+
z=parsed_offsets[offset_key].dropOffset.z,
|
|
1318
|
+
),
|
|
1319
|
+
)
|
|
1320
|
+
|
|
1321
|
+
def get_grip_force(self, labware_definition: LabwareDefinition) -> float:
|
|
1322
|
+
"""Get the recommended grip force for gripping labware using gripper."""
|
|
1323
|
+
recommended_force = labware_definition.gripForce
|
|
1324
|
+
return (
|
|
1325
|
+
recommended_force if recommended_force is not None else LABWARE_GRIP_FORCE
|
|
1326
|
+
)
|
|
1327
|
+
|
|
1328
|
+
def get_grip_z(self, labware_definition: LabwareDefinition) -> float:
|
|
1329
|
+
"""Get the place on the labware where the gripper should contact.
|
|
1330
|
+
|
|
1331
|
+
The returned value is a z-offset relative to the labware origin.
|
|
1332
|
+
"""
|
|
1333
|
+
|
|
1334
|
+
def get_origin_to_mid_z(labware_definition: LabwareDefinition) -> float:
|
|
1335
|
+
"""Return the z-coordinate of the middle of the labware, relative to the labware's origin."""
|
|
1336
|
+
extents = self.get_extents_around_lw_origin(labware_definition)
|
|
1337
|
+
return (extents.max_z + extents.min_z) / 2
|
|
1338
|
+
|
|
1339
|
+
if labware_definition.schemaVersion == 2:
|
|
1340
|
+
# In schema 2, the bottom of the labware is at the z-origin by definition.
|
|
1341
|
+
defined_height_from_origin = labware_definition.gripHeightFromLabwareBottom
|
|
1342
|
+
else:
|
|
1343
|
+
defined_height_from_origin = labware_definition.gripHeightFromLabwareOrigin
|
|
1344
|
+
|
|
1345
|
+
return (
|
|
1346
|
+
defined_height_from_origin
|
|
1347
|
+
if defined_height_from_origin is not None
|
|
1348
|
+
else get_origin_to_mid_z(labware_definition)
|
|
1349
|
+
)
|
|
1350
|
+
|
|
1351
|
+
@staticmethod
|
|
1352
|
+
def _max_x_of_well(well_defn: _WellDefinition) -> float:
|
|
1353
|
+
if well_defn.shape == "rectangular":
|
|
1354
|
+
return well_defn.x + (well_defn.xDimension or 0) / 2
|
|
1355
|
+
elif well_defn.shape == "circular":
|
|
1356
|
+
return well_defn.x + (well_defn.diameter or 0) / 2
|
|
1357
|
+
else:
|
|
1358
|
+
return well_defn.x
|
|
1359
|
+
|
|
1360
|
+
@staticmethod
|
|
1361
|
+
def _min_x_of_well(well_defn: _WellDefinition) -> float:
|
|
1362
|
+
if well_defn.shape == "rectangular":
|
|
1363
|
+
return well_defn.x - (well_defn.xDimension or 0) / 2
|
|
1364
|
+
elif well_defn.shape == "circular":
|
|
1365
|
+
return well_defn.x - (well_defn.diameter or 0) / 2
|
|
1366
|
+
else:
|
|
1367
|
+
return 0
|
|
1368
|
+
|
|
1369
|
+
@staticmethod
|
|
1370
|
+
def _max_y_of_well(well_defn: _WellDefinition) -> float:
|
|
1371
|
+
if well_defn.shape == "rectangular":
|
|
1372
|
+
return well_defn.y + (well_defn.yDimension or 0) / 2
|
|
1373
|
+
elif well_defn.shape == "circular":
|
|
1374
|
+
return well_defn.y + (well_defn.diameter or 0) / 2
|
|
1375
|
+
else:
|
|
1376
|
+
return 0
|
|
1377
|
+
|
|
1378
|
+
@staticmethod
|
|
1379
|
+
def _min_y_of_well(well_defn: _WellDefinition) -> float:
|
|
1380
|
+
if well_defn.shape == "rectangular":
|
|
1381
|
+
return well_defn.y - (well_defn.yDimension or 0) / 2
|
|
1382
|
+
elif well_defn.shape == "circular":
|
|
1383
|
+
return well_defn.y - (well_defn.diameter or 0) / 2
|
|
1384
|
+
else:
|
|
1385
|
+
return 0
|
|
1386
|
+
|
|
1387
|
+
@staticmethod
|
|
1388
|
+
def _max_z_of_well(well_defn: _WellDefinition) -> float:
|
|
1389
|
+
return well_defn.z + well_defn.depth
|
|
1390
|
+
|
|
1391
|
+
def get_well_bbox(self, labware_definition: LabwareDefinition) -> Dimensions:
|
|
1392
|
+
"""Get the bounding box implied by the wells.
|
|
1393
|
+
|
|
1394
|
+
The bounding box of the labware that is implied by the wells is that required
|
|
1395
|
+
to contain the bounds of the wells - the y-span from the min-y bound of the min-y
|
|
1396
|
+
well to the max-y bound of the max-y well, x ditto, z from labware 0 to the max-z
|
|
1397
|
+
well top.
|
|
1398
|
+
|
|
1399
|
+
This is used for the specific purpose of finding the reasonable uncertainty bounds of
|
|
1400
|
+
where and how a gripper will interact with a labware.
|
|
1401
|
+
"""
|
|
1402
|
+
max_x: Optional[float] = None
|
|
1403
|
+
min_x: Optional[float] = None
|
|
1404
|
+
max_y: Optional[float] = None
|
|
1405
|
+
min_y: Optional[float] = None
|
|
1406
|
+
max_z: Optional[float] = None
|
|
1407
|
+
|
|
1408
|
+
for well in labware_definition.wells.values():
|
|
1409
|
+
well_max_x = self._max_x_of_well(well)
|
|
1410
|
+
well_min_x = self._min_x_of_well(well)
|
|
1411
|
+
well_max_y = self._max_y_of_well(well)
|
|
1412
|
+
well_min_y = self._min_y_of_well(well)
|
|
1413
|
+
well_max_z = self._max_z_of_well(well)
|
|
1414
|
+
if (max_x is None) or (well_max_x > max_x):
|
|
1415
|
+
max_x = well_max_x
|
|
1416
|
+
if (max_y is None) or (well_max_y > max_y):
|
|
1417
|
+
max_y = well_max_y
|
|
1418
|
+
if (min_x is None) or (well_min_x < min_x):
|
|
1419
|
+
min_x = well_min_x
|
|
1420
|
+
if (min_y is None) or (well_min_y < min_y):
|
|
1421
|
+
min_y = well_min_y
|
|
1422
|
+
if (max_z is None) or (well_max_z > max_z):
|
|
1423
|
+
max_z = well_max_z
|
|
1424
|
+
if (
|
|
1425
|
+
max_x is None
|
|
1426
|
+
or max_y is None
|
|
1427
|
+
or min_x is None
|
|
1428
|
+
or min_y is None
|
|
1429
|
+
or max_z is None
|
|
1430
|
+
):
|
|
1431
|
+
return Dimensions(0, 0, 0)
|
|
1432
|
+
return Dimensions(max_x - min_x, max_y - min_y, max_z)
|