opentrons 8.6.0a1__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.

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