opentrons 8.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

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