opentrons 8.2.0a3__py2.py3-none-any.whl → 8.3.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. opentrons/calibration_storage/deck_configuration.py +3 -3
  2. opentrons/calibration_storage/file_operators.py +3 -3
  3. opentrons/calibration_storage/helpers.py +3 -1
  4. opentrons/calibration_storage/ot2/models/v1.py +16 -29
  5. opentrons/calibration_storage/ot2/tip_length.py +7 -4
  6. opentrons/calibration_storage/ot3/models/v1.py +14 -23
  7. opentrons/cli/analyze.py +18 -6
  8. opentrons/config/defaults_ot3.py +1 -0
  9. opentrons/drivers/asyncio/communication/__init__.py +2 -0
  10. opentrons/drivers/asyncio/communication/errors.py +16 -3
  11. opentrons/drivers/asyncio/communication/serial_connection.py +24 -9
  12. opentrons/drivers/command_builder.py +2 -2
  13. opentrons/drivers/flex_stacker/__init__.py +9 -0
  14. opentrons/drivers/flex_stacker/abstract.py +89 -0
  15. opentrons/drivers/flex_stacker/driver.py +260 -0
  16. opentrons/drivers/flex_stacker/simulator.py +109 -0
  17. opentrons/drivers/flex_stacker/types.py +138 -0
  18. opentrons/drivers/heater_shaker/driver.py +18 -3
  19. opentrons/drivers/temp_deck/driver.py +13 -3
  20. opentrons/drivers/thermocycler/driver.py +17 -3
  21. opentrons/execute.py +3 -1
  22. opentrons/hardware_control/__init__.py +1 -2
  23. opentrons/hardware_control/api.py +33 -21
  24. opentrons/hardware_control/backends/flex_protocol.py +17 -7
  25. opentrons/hardware_control/backends/ot3controller.py +213 -63
  26. opentrons/hardware_control/backends/ot3simulator.py +18 -9
  27. opentrons/hardware_control/backends/ot3utils.py +43 -15
  28. opentrons/hardware_control/dev_types.py +4 -0
  29. opentrons/hardware_control/emulation/heater_shaker.py +4 -0
  30. opentrons/hardware_control/emulation/module_server/client.py +1 -1
  31. opentrons/hardware_control/emulation/module_server/server.py +5 -3
  32. opentrons/hardware_control/emulation/settings.py +3 -4
  33. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +2 -1
  34. opentrons/hardware_control/instruments/ot2/pipette.py +15 -22
  35. opentrons/hardware_control/instruments/ot2/pipette_handler.py +8 -1
  36. opentrons/hardware_control/instruments/ot3/gripper.py +2 -2
  37. opentrons/hardware_control/instruments/ot3/pipette.py +23 -22
  38. opentrons/hardware_control/instruments/ot3/pipette_handler.py +10 -1
  39. opentrons/hardware_control/modules/mod_abc.py +2 -2
  40. opentrons/hardware_control/motion_utilities.py +68 -0
  41. opentrons/hardware_control/nozzle_manager.py +39 -41
  42. opentrons/hardware_control/ot3_calibration.py +1 -1
  43. opentrons/hardware_control/ot3api.py +78 -31
  44. opentrons/hardware_control/protocols/gripper_controller.py +3 -0
  45. opentrons/hardware_control/protocols/hardware_manager.py +5 -1
  46. opentrons/hardware_control/protocols/liquid_handler.py +22 -1
  47. opentrons/hardware_control/protocols/motion_controller.py +7 -0
  48. opentrons/hardware_control/robot_calibration.py +1 -1
  49. opentrons/hardware_control/types.py +61 -0
  50. opentrons/legacy_commands/commands.py +37 -0
  51. opentrons/legacy_commands/types.py +39 -0
  52. opentrons/protocol_api/__init__.py +20 -1
  53. opentrons/protocol_api/_liquid.py +24 -49
  54. opentrons/protocol_api/_liquid_properties.py +754 -0
  55. opentrons/protocol_api/_types.py +24 -0
  56. opentrons/protocol_api/core/common.py +2 -0
  57. opentrons/protocol_api/core/engine/instrument.py +191 -10
  58. opentrons/protocol_api/core/engine/labware.py +29 -7
  59. opentrons/protocol_api/core/engine/protocol.py +130 -5
  60. opentrons/protocol_api/core/engine/robot.py +139 -0
  61. opentrons/protocol_api/core/engine/well.py +4 -1
  62. opentrons/protocol_api/core/instrument.py +73 -4
  63. opentrons/protocol_api/core/labware.py +13 -4
  64. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +87 -3
  65. opentrons/protocol_api/core/legacy/legacy_labware_core.py +13 -4
  66. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +32 -1
  67. opentrons/protocol_api/core/legacy/legacy_robot_core.py +0 -0
  68. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +61 -3
  69. opentrons/protocol_api/core/protocol.py +34 -1
  70. opentrons/protocol_api/core/robot.py +51 -0
  71. opentrons/protocol_api/instrument_context.py +299 -44
  72. opentrons/protocol_api/labware.py +248 -9
  73. opentrons/protocol_api/module_contexts.py +21 -17
  74. opentrons/protocol_api/protocol_context.py +125 -4
  75. opentrons/protocol_api/robot_context.py +204 -32
  76. opentrons/protocol_api/validation.py +262 -3
  77. opentrons/protocol_engine/__init__.py +4 -0
  78. opentrons/protocol_engine/actions/actions.py +2 -3
  79. opentrons/protocol_engine/clients/sync_client.py +18 -0
  80. opentrons/protocol_engine/commands/__init__.py +121 -0
  81. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +1 -3
  82. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +20 -6
  83. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +1 -2
  84. opentrons/protocol_engine/commands/absorbance_reader/read.py +40 -10
  85. opentrons/protocol_engine/commands/air_gap_in_place.py +160 -0
  86. opentrons/protocol_engine/commands/aspirate.py +103 -53
  87. opentrons/protocol_engine/commands/aspirate_in_place.py +55 -51
  88. opentrons/protocol_engine/commands/blow_out.py +44 -39
  89. opentrons/protocol_engine/commands/blow_out_in_place.py +21 -32
  90. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +13 -6
  91. opentrons/protocol_engine/commands/calibration/calibrate_module.py +1 -1
  92. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +3 -3
  93. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +1 -1
  94. opentrons/protocol_engine/commands/command.py +73 -66
  95. opentrons/protocol_engine/commands/command_unions.py +140 -1
  96. opentrons/protocol_engine/commands/comment.py +1 -1
  97. opentrons/protocol_engine/commands/configure_for_volume.py +10 -3
  98. opentrons/protocol_engine/commands/configure_nozzle_layout.py +6 -4
  99. opentrons/protocol_engine/commands/custom.py +6 -12
  100. opentrons/protocol_engine/commands/dispense.py +82 -48
  101. opentrons/protocol_engine/commands/dispense_in_place.py +71 -51
  102. opentrons/protocol_engine/commands/drop_tip.py +52 -31
  103. opentrons/protocol_engine/commands/drop_tip_in_place.py +79 -8
  104. opentrons/protocol_engine/commands/evotip_dispense.py +156 -0
  105. opentrons/protocol_engine/commands/evotip_seal_pipette.py +331 -0
  106. opentrons/protocol_engine/commands/evotip_unseal_pipette.py +160 -0
  107. opentrons/protocol_engine/commands/generate_command_schema.py +4 -11
  108. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  109. opentrons/protocol_engine/commands/get_tip_presence.py +1 -1
  110. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +1 -1
  111. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +1 -1
  112. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +1 -1
  113. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +1 -1
  114. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +1 -1
  115. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +1 -1
  116. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +10 -4
  117. opentrons/protocol_engine/commands/home.py +13 -4
  118. opentrons/protocol_engine/commands/liquid_probe.py +125 -31
  119. opentrons/protocol_engine/commands/load_labware.py +33 -6
  120. opentrons/protocol_engine/commands/load_lid.py +146 -0
  121. opentrons/protocol_engine/commands/load_lid_stack.py +189 -0
  122. opentrons/protocol_engine/commands/load_liquid.py +12 -4
  123. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  124. opentrons/protocol_engine/commands/load_module.py +31 -10
  125. opentrons/protocol_engine/commands/load_pipette.py +19 -8
  126. opentrons/protocol_engine/commands/magnetic_module/disengage.py +1 -1
  127. opentrons/protocol_engine/commands/magnetic_module/engage.py +1 -1
  128. opentrons/protocol_engine/commands/move_labware.py +28 -6
  129. opentrons/protocol_engine/commands/move_relative.py +35 -25
  130. opentrons/protocol_engine/commands/move_to_addressable_area.py +40 -27
  131. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +53 -32
  132. opentrons/protocol_engine/commands/move_to_coordinates.py +36 -22
  133. opentrons/protocol_engine/commands/move_to_well.py +40 -24
  134. opentrons/protocol_engine/commands/movement_common.py +338 -0
  135. opentrons/protocol_engine/commands/pick_up_tip.py +49 -27
  136. opentrons/protocol_engine/commands/pipetting_common.py +169 -87
  137. opentrons/protocol_engine/commands/prepare_to_aspirate.py +24 -33
  138. opentrons/protocol_engine/commands/reload_labware.py +1 -1
  139. opentrons/protocol_engine/commands/retract_axis.py +1 -1
  140. opentrons/protocol_engine/commands/robot/__init__.py +69 -0
  141. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +86 -0
  142. opentrons/protocol_engine/commands/robot/common.py +18 -0
  143. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  144. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  145. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  146. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +77 -0
  147. opentrons/protocol_engine/commands/save_position.py +14 -5
  148. opentrons/protocol_engine/commands/set_rail_lights.py +1 -1
  149. opentrons/protocol_engine/commands/set_status_bar.py +1 -1
  150. opentrons/protocol_engine/commands/temperature_module/deactivate.py +1 -1
  151. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +1 -1
  152. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +10 -4
  153. opentrons/protocol_engine/commands/thermocycler/close_lid.py +1 -1
  154. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +1 -1
  155. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +1 -1
  156. opentrons/protocol_engine/commands/thermocycler/open_lid.py +1 -1
  157. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +9 -3
  158. opentrons/protocol_engine/commands/thermocycler/run_profile.py +9 -3
  159. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +11 -4
  160. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +1 -1
  161. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +1 -1
  162. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +1 -1
  163. opentrons/protocol_engine/commands/touch_tip.py +65 -16
  164. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +5 -2
  165. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +13 -4
  166. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +2 -5
  167. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +1 -1
  168. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +4 -2
  169. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +2 -5
  170. opentrons/protocol_engine/commands/verify_tip_presence.py +11 -4
  171. opentrons/protocol_engine/commands/wait_for_duration.py +10 -3
  172. opentrons/protocol_engine/commands/wait_for_resume.py +10 -3
  173. opentrons/protocol_engine/errors/__init__.py +12 -0
  174. opentrons/protocol_engine/errors/error_occurrence.py +19 -20
  175. opentrons/protocol_engine/errors/exceptions.py +76 -0
  176. opentrons/protocol_engine/execution/command_executor.py +1 -1
  177. opentrons/protocol_engine/execution/equipment.py +73 -5
  178. opentrons/protocol_engine/execution/gantry_mover.py +369 -8
  179. opentrons/protocol_engine/execution/hardware_stopper.py +7 -7
  180. opentrons/protocol_engine/execution/movement.py +27 -0
  181. opentrons/protocol_engine/execution/pipetting.py +5 -1
  182. opentrons/protocol_engine/execution/tip_handler.py +34 -15
  183. opentrons/protocol_engine/notes/notes.py +1 -1
  184. opentrons/protocol_engine/protocol_engine.py +7 -6
  185. opentrons/protocol_engine/resources/labware_data_provider.py +1 -1
  186. opentrons/protocol_engine/resources/labware_validation.py +18 -0
  187. opentrons/protocol_engine/resources/module_data_provider.py +1 -1
  188. opentrons/protocol_engine/resources/pipette_data_provider.py +26 -0
  189. opentrons/protocol_engine/slot_standardization.py +9 -9
  190. opentrons/protocol_engine/state/_move_types.py +9 -5
  191. opentrons/protocol_engine/state/_well_math.py +193 -0
  192. opentrons/protocol_engine/state/addressable_areas.py +25 -61
  193. opentrons/protocol_engine/state/command_history.py +12 -0
  194. opentrons/protocol_engine/state/commands.py +22 -14
  195. opentrons/protocol_engine/state/files.py +10 -12
  196. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  197. opentrons/protocol_engine/state/frustum_helpers.py +63 -69
  198. opentrons/protocol_engine/state/geometry.py +47 -1
  199. opentrons/protocol_engine/state/labware.py +92 -26
  200. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  201. opentrons/protocol_engine/state/liquids.py +16 -4
  202. opentrons/protocol_engine/state/modules.py +56 -71
  203. opentrons/protocol_engine/state/motion.py +6 -1
  204. opentrons/protocol_engine/state/pipettes.py +149 -58
  205. opentrons/protocol_engine/state/state.py +21 -2
  206. opentrons/protocol_engine/state/state_summary.py +4 -2
  207. opentrons/protocol_engine/state/tips.py +11 -44
  208. opentrons/protocol_engine/state/update_types.py +343 -48
  209. opentrons/protocol_engine/state/wells.py +19 -11
  210. opentrons/protocol_engine/types.py +176 -28
  211. opentrons/protocol_reader/extract_labware_definitions.py +5 -2
  212. opentrons/protocol_reader/file_format_validator.py +5 -5
  213. opentrons/protocol_runner/json_file_reader.py +9 -3
  214. opentrons/protocol_runner/json_translator.py +51 -25
  215. opentrons/protocol_runner/legacy_command_mapper.py +66 -64
  216. opentrons/protocol_runner/protocol_runner.py +35 -4
  217. opentrons/protocol_runner/python_protocol_wrappers.py +1 -1
  218. opentrons/protocol_runner/run_orchestrator.py +13 -3
  219. opentrons/protocols/advanced_control/common.py +38 -0
  220. opentrons/protocols/advanced_control/mix.py +1 -1
  221. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  222. opentrons/protocols/advanced_control/transfers/common.py +56 -0
  223. opentrons/protocols/advanced_control/{transfers.py → transfers/transfer.py} +10 -85
  224. opentrons/protocols/api_support/definitions.py +1 -1
  225. opentrons/protocols/api_support/instrument.py +1 -1
  226. opentrons/protocols/api_support/util.py +10 -0
  227. opentrons/protocols/labware.py +70 -8
  228. opentrons/protocols/models/json_protocol.py +5 -9
  229. opentrons/simulate.py +3 -1
  230. opentrons/types.py +162 -2
  231. opentrons/util/entrypoint_util.py +2 -5
  232. opentrons/util/logging_config.py +1 -1
  233. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/METADATA +16 -15
  234. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/RECORD +238 -208
  235. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/WHEEL +1 -1
  236. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/LICENSE +0 -0
  237. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/entry_points.txt +0 -0
  238. {opentrons-8.2.0a3.dist-info → opentrons-8.3.0.dist-info}/top_level.txt +0 -0
@@ -35,7 +35,7 @@ def make_error_recovery_debug_note(type: "ErrorRecoveryType") -> CommandNote:
35
35
  This is intended to be read by developers and support people, not computers.
36
36
  """
37
37
  message = f"Handling this command failure with {type.name}."
38
- return CommandNote.construct(
38
+ return CommandNote.model_construct(
39
39
  noteKind="debugErrorRecovery",
40
40
  shortMessage=message,
41
41
  longMessage=message,
@@ -30,7 +30,6 @@ from .types import (
30
30
  HexColor,
31
31
  PostRunHardwareState,
32
32
  DeckConfigurationType,
33
- AddressableAreaLocation,
34
33
  )
35
34
  from .execution import (
36
35
  QueueWorker,
@@ -427,7 +426,7 @@ class ProtocolEngine:
427
426
  post_run_hardware_state: The state in which to leave the gantry and motors in
428
427
  after the run is over.
429
428
  """
430
- if self._state_store.commands.state.stopped_by_estop:
429
+ if self._state_store.commands.get_is_stopped_by_estop():
431
430
  # This handles the case where the E-stop was pressed while we were *not* in the middle
432
431
  # of some hardware interaction that would raise it as an exception. For example, imagine
433
432
  # we were paused between two commands, or imagine we were executing a waitForDuration.
@@ -565,15 +564,17 @@ class ProtocolEngine:
565
564
  description=(description or ""),
566
565
  displayColor=color,
567
566
  )
567
+ validated_liquid = self._state_store.liquid.validate_liquid_allowed(
568
+ liquid=liquid
569
+ )
568
570
 
569
- self._action_dispatcher.dispatch(AddLiquidAction(liquid=liquid))
570
- return liquid
571
+ self._action_dispatcher.dispatch(AddLiquidAction(liquid=validated_liquid))
572
+ return validated_liquid
571
573
 
572
574
  def add_addressable_area(self, addressable_area_name: str) -> None:
573
575
  """Add an addressable area to state."""
574
- area = AddressableAreaLocation(addressableAreaName=addressable_area_name)
575
576
  self._action_dispatcher.dispatch(
576
- AddAddressableAreaAction(addressable_area=area)
577
+ AddAddressableAreaAction(addressable_area_name)
577
578
  )
578
579
 
579
580
  def reset_tips(self, labware_id: str) -> None:
@@ -44,7 +44,7 @@ class LabwareDataProvider:
44
44
  def _get_labware_definition_sync(
45
45
  load_name: str, namespace: str, version: int
46
46
  ) -> LabwareDefinition:
47
- return LabwareDefinition.parse_obj(
47
+ return LabwareDefinition.model_validate(
48
48
  get_labware_definition(load_name, namespace, version)
49
49
  )
50
50
 
@@ -14,6 +14,11 @@ def is_absorbance_reader_lid(load_name: str) -> bool:
14
14
  return load_name == "opentrons_flex_lid_absorbance_plate_reader_module"
15
15
 
16
16
 
17
+ def is_evotips(load_name: str) -> bool:
18
+ """Check if a labware is an evotips tiprack."""
19
+ return load_name == "evotips_opentrons_96_labware"
20
+
21
+
17
22
  def validate_definition_is_labware(definition: LabwareDefinition) -> bool:
18
23
  """Validate that one of the definition's allowed roles is `labware`.
19
24
 
@@ -32,6 +37,11 @@ def validate_definition_is_lid(definition: LabwareDefinition) -> bool:
32
37
  return LabwareRole.lid in definition.allowedRoles
33
38
 
34
39
 
40
+ def validate_definition_is_system(definition: LabwareDefinition) -> bool:
41
+ """Validate that one of the definition's allowed roles is `system`."""
42
+ return LabwareRole.system in definition.allowedRoles
43
+
44
+
35
45
  def validate_labware_can_be_stacked(
36
46
  top_labware_definition: LabwareDefinition, below_labware_load_name: str
37
47
  ) -> bool:
@@ -39,6 +49,14 @@ def validate_labware_can_be_stacked(
39
49
  return below_labware_load_name in top_labware_definition.stackingOffsetWithLabware
40
50
 
41
51
 
52
+ def validate_labware_can_be_ondeck(definition: LabwareDefinition) -> bool:
53
+ """Validate that the labware being loaded onto the deck can sit in a slot."""
54
+ return (
55
+ definition.parameters.quirks is None
56
+ or "stackingOnly" not in definition.parameters.quirks
57
+ )
58
+
59
+
42
60
  def validate_gripper_compatible(definition: LabwareDefinition) -> bool:
43
61
  """Validate that the labware definition does not have a quirk disallowing movement with gripper."""
44
62
  return (
@@ -22,7 +22,7 @@ class ModuleDataProvider:
22
22
  def get_definition(model: ModuleModel) -> ModuleDefinition:
23
23
  """Get the module definition."""
24
24
  data = load_definition(model_or_loadname=model.value, version="3")
25
- return ModuleDefinition.parse_obj(data)
25
+ return ModuleDefinition.model_validate(data)
26
26
 
27
27
  @staticmethod
28
28
  def load_module_calibrations() -> Dict[str, ModuleOffsetData]:
@@ -67,6 +67,9 @@ class LoadedStaticPipetteData:
67
67
  back_left_corner_offset: Point
68
68
  front_right_corner_offset: Point
69
69
  pipette_lld_settings: Optional[Dict[str, Dict[str, float]]]
70
+ plunger_positions: Dict[str, float]
71
+ shaft_ul_per_mm: float
72
+ available_sensors: pipette_definition.AvailableSensorDefinition
70
73
 
71
74
 
72
75
  class VirtualPipetteDataProvider:
@@ -95,6 +98,7 @@ class VirtualPipetteDataProvider:
95
98
  config.pipette_type,
96
99
  config.channels,
97
100
  config.version,
101
+ pip_types.PipetteOEMType.OT,
98
102
  )
99
103
  new_nozzle_manager = NozzleConfigurationManager.build_from_config(
100
104
  config, valid_nozzle_maps
@@ -127,6 +131,7 @@ class VirtualPipetteDataProvider:
127
131
  pipette_model.pipette_type,
128
132
  pipette_model.pipette_channels,
129
133
  pipette_model.pipette_version,
134
+ pipette_model.oem_type,
130
135
  )
131
136
 
132
137
  liquid_class = pipette_definition.liquid_class_for_volume_between_default_and_defaultlowvolume(
@@ -160,6 +165,7 @@ class VirtualPipetteDataProvider:
160
165
  pipette_model.pipette_type,
161
166
  pipette_model.pipette_channels,
162
167
  pipette_model.pipette_version,
168
+ pipette_model.oem_type,
163
169
  )
164
170
 
165
171
  def _get_virtual_pipette_static_config_by_model( # noqa: C901
@@ -176,6 +182,7 @@ class VirtualPipetteDataProvider:
176
182
  pipette_model.pipette_type,
177
183
  pipette_model.pipette_channels,
178
184
  pipette_model.pipette_version,
185
+ pipette_model.oem_type,
179
186
  )
180
187
  try:
181
188
  tip_type = pip_types.PipetteTipType(
@@ -192,6 +199,7 @@ class VirtualPipetteDataProvider:
192
199
  pipette_model.pipette_type,
193
200
  pipette_model.pipette_channels,
194
201
  pipette_model.pipette_version,
202
+ pipette_model.oem_type,
195
203
  )
196
204
  if pipette_id not in self._nozzle_manager_layout_by_id:
197
205
  nozzle_manager = NozzleConfigurationManager.build_from_config(
@@ -252,6 +260,7 @@ class VirtualPipetteDataProvider:
252
260
 
253
261
  pip_back_left = config.pipette_bounding_box_offsets.back_left_corner
254
262
  pip_front_right = config.pipette_bounding_box_offsets.front_right_corner
263
+ plunger_positions = config.plunger_positions_configurations[liquid_class]
255
264
  return LoadedStaticPipetteData(
256
265
  model=str(pipette_model),
257
266
  display_name=config.display_name,
@@ -280,6 +289,15 @@ class VirtualPipetteDataProvider:
280
289
  pip_front_right[0], pip_front_right[1], pip_front_right[2]
281
290
  ),
282
291
  pipette_lld_settings=config.lld_settings,
292
+ plunger_positions={
293
+ "top": plunger_positions.top,
294
+ "bottom": plunger_positions.bottom,
295
+ "blow_out": plunger_positions.blow_out,
296
+ "drop_tip": plunger_positions.drop_tip,
297
+ },
298
+ shaft_ul_per_mm=config.shaft_ul_per_mm,
299
+ available_sensors=config.available_sensors
300
+ or pipette_definition.AvailableSensorDefinition(sensors=[]),
283
301
  )
284
302
 
285
303
  def get_virtual_pipette_static_config(
@@ -298,6 +316,11 @@ def get_pipette_static_config(
298
316
  """Get the config for a pipette, given the state/config object from the HW API."""
299
317
  back_left_offset = pipette_dict["pipette_bounding_box_offsets"].back_left_corner
300
318
  front_right_offset = pipette_dict["pipette_bounding_box_offsets"].front_right_corner
319
+ available_sensors = (
320
+ pipette_dict["available_sensors"]
321
+ if "available_sensors" in pipette_dict.keys()
322
+ else pipette_definition.AvailableSensorDefinition(sensors=[])
323
+ )
301
324
  return LoadedStaticPipetteData(
302
325
  model=pipette_dict["model"],
303
326
  display_name=pipette_dict["display_name"],
@@ -327,6 +350,9 @@ def get_pipette_static_config(
327
350
  front_right_offset[0], front_right_offset[1], front_right_offset[2]
328
351
  ),
329
352
  pipette_lld_settings=pipette_dict["lld_settings"],
353
+ plunger_positions=pipette_dict["plunger_positions"],
354
+ shaft_ul_per_mm=pipette_dict["shaft_ul_per_mm"],
355
+ available_sensors=available_sensors,
330
356
  )
331
357
 
332
358
 
@@ -35,9 +35,9 @@ def standardize_labware_offset(
35
35
  original: LabwareOffsetCreate, robot_type: RobotType
36
36
  ) -> LabwareOffsetCreate:
37
37
  """Convert the deck slot in the given `LabwareOffsetCreate` to match the given robot type."""
38
- return original.copy(
38
+ return original.model_copy(
39
39
  update={
40
- "location": original.location.copy(
40
+ "location": original.location.model_copy(
41
41
  update={
42
42
  "slotName": original.location.slotName.to_equivalent_for_robot_type(
43
43
  robot_type
@@ -70,40 +70,40 @@ def standardize_command(
70
70
  def _standardize_load_labware(
71
71
  original: commands.LoadLabwareCreate, robot_type: RobotType
72
72
  ) -> commands.LoadLabwareCreate:
73
- params = original.params.copy(
73
+ params = original.params.model_copy(
74
74
  update={
75
75
  "location": _standardize_labware_location(
76
76
  original.params.location, robot_type
77
77
  )
78
78
  }
79
79
  )
80
- return original.copy(update={"params": params})
80
+ return original.model_copy(update={"params": params})
81
81
 
82
82
 
83
83
  def _standardize_load_module(
84
84
  original: commands.LoadModuleCreate, robot_type: RobotType
85
85
  ) -> commands.LoadModuleCreate:
86
- params = original.params.copy(
86
+ params = original.params.model_copy(
87
87
  update={
88
88
  "location": _standardize_deck_slot_location(
89
89
  original.params.location, robot_type
90
90
  )
91
91
  }
92
92
  )
93
- return original.copy(update={"params": params})
93
+ return original.model_copy(update={"params": params})
94
94
 
95
95
 
96
96
  def _standardize_move_labware(
97
97
  original: commands.MoveLabwareCreate, robot_type: RobotType
98
98
  ) -> commands.MoveLabwareCreate:
99
- params = original.params.copy(
99
+ params = original.params.model_copy(
100
100
  update={
101
101
  "newLocation": _standardize_labware_location(
102
102
  original.params.newLocation, robot_type
103
103
  )
104
104
  }
105
105
  )
106
- return original.copy(update={"params": params})
106
+ return original.model_copy(update={"params": params})
107
107
 
108
108
 
109
109
  _standardize_command_functions: Dict[
@@ -135,6 +135,6 @@ def _standardize_labware_location(
135
135
  def _standardize_deck_slot_location(
136
136
  original: DeckSlotLocation, robot_type: RobotType
137
137
  ) -> DeckSlotLocation:
138
- return original.copy(
138
+ return original.model_copy(
139
139
  update={"slotName": original.slotName.to_equivalent_for_robot_type(robot_type)}
140
140
  )
@@ -53,15 +53,19 @@ def get_move_type_to_well(
53
53
 
54
54
 
55
55
  def get_edge_point_list(
56
- center: Point, x_radius: float, y_radius: float, edge_path_type: EdgePathType
56
+ center: Point,
57
+ x_radius: float,
58
+ y_radius: float,
59
+ mm_from_edge: float,
60
+ edge_path_type: EdgePathType,
57
61
  ) -> List[Point]:
58
62
  """Get list of edge points dependent on edge path type."""
59
63
  edges = EdgeList(
60
- right=center + Point(x=x_radius, y=0, z=0),
61
- left=center + Point(x=-x_radius, y=0, z=0),
64
+ right=center + Point(x=x_radius - mm_from_edge, y=0, z=0),
65
+ left=center + Point(x=-x_radius + mm_from_edge, y=0, z=0),
62
66
  center=center,
63
- forward=center + Point(x=0, y=y_radius, z=0),
64
- back=center + Point(x=0, y=-y_radius, z=0),
67
+ forward=center + Point(x=0, y=y_radius - mm_from_edge, z=0),
68
+ back=center + Point(x=0, y=-y_radius + mm_from_edge, z=0),
65
69
  )
66
70
 
67
71
  if edge_path_type == EdgePathType.LEFT:
@@ -0,0 +1,193 @@
1
+ """Utilities for doing coverage math on wells."""
2
+
3
+ from typing import Iterator
4
+ from opentrons_shared_data.errors.exceptions import (
5
+ InvalidStoredData,
6
+ InvalidProtocolData,
7
+ )
8
+
9
+ from opentrons.hardware_control.nozzle_manager import NozzleMap
10
+
11
+
12
+ def wells_covered_by_pipette_configuration(
13
+ nozzle_map: NozzleMap,
14
+ target_well: str,
15
+ labware_wells_by_column: list[list[str]],
16
+ ) -> Iterator[str]:
17
+ """Compute the wells covered by a pipette nozzle configuration."""
18
+ if len(labware_wells_by_column) >= 12 and len(labware_wells_by_column[0]) >= 8:
19
+ yield from wells_covered_dense(
20
+ nozzle_map,
21
+ target_well,
22
+ labware_wells_by_column,
23
+ )
24
+ elif len(labware_wells_by_column) < 12 and len(labware_wells_by_column[0]) < 8:
25
+ yield from wells_covered_sparse(
26
+ nozzle_map, target_well, labware_wells_by_column
27
+ )
28
+ else:
29
+ raise InvalidStoredData(
30
+ "Labware of non-SBS and non-reservoir format cannot be handled"
31
+ )
32
+
33
+
34
+ def row_col_ordinals_from_column_major_map(
35
+ target_well: str, column_major_wells: list[list[str]]
36
+ ) -> tuple[int, int]:
37
+ """Turn a well name into the index of its row and column (in that order) within the labware."""
38
+ for column_index, column in enumerate(column_major_wells):
39
+ if target_well in column:
40
+ return column.index(target_well), column_index
41
+ raise InvalidStoredData(f"Well name {target_well} is not present in labware")
42
+
43
+
44
+ def wells_covered_dense( # noqa: C901
45
+ nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]]
46
+ ) -> Iterator[str]:
47
+ """Get the list of wells covered by a nozzle map on an SBS format labware with a specified multiplier of 96 into the number of wells.
48
+
49
+ This will handle the offsetting of the nozzle map into higher-density well plates. For instance, a full column config target at A1 of a
50
+ 96 plate would cover wells A1, B1, C1, D1, E1, F1, G1, H1, and use downsample_factor 1.0 (96*1 = 96). A full column config target on a
51
+ 384 plate would cover wells A1, C1, E1, G1, I1, K1, M1, O1 and use downsample_factor 4.0 (96*4 = 384), while a full column config
52
+ targeting B1 would cover wells B1, D1, F1, H1, J1, L1, N1, P1 - still using downsample_factor 4.0, with the offset gathered from the
53
+ target well.
54
+
55
+ The function may also handle sub-96 regular labware with fractional downsample factors, but that's physically improbable and it's not
56
+ tested. If you have a regular labware with fewer than 96 wells that is still regularly-spaced and has little enough space between well
57
+ walls that it's reasonable to use with multiple channels, you probably want wells_covered_trough.
58
+ """
59
+ target_row_index, target_column_index = row_col_ordinals_from_column_major_map(
60
+ target_well, target_wells_by_column
61
+ )
62
+ column_downsample = len(target_wells_by_column) // 12
63
+ row_downsample = len(target_wells_by_column[0]) // 8
64
+ if column_downsample < 1 or row_downsample < 1:
65
+ raise InvalidStoredData(
66
+ "This labware cannot be used wells_covered_dense because it is less dense than an SBS 96 standard"
67
+ )
68
+
69
+ for nozzle_column in range(len(nozzle_map.columns)):
70
+ target_column_offset = nozzle_column * column_downsample
71
+ for nozzle_row in range(len(nozzle_map.rows)):
72
+ target_row_offset = nozzle_row * row_downsample
73
+ if nozzle_map.starting_nozzle == "A1":
74
+ if (
75
+ target_column_index + target_column_offset
76
+ < len(target_wells_by_column)
77
+ ) and (
78
+ target_row_index + target_row_offset
79
+ < len(target_wells_by_column[target_column_index])
80
+ ):
81
+ yield target_wells_by_column[
82
+ target_column_index + target_column_offset
83
+ ][target_row_index + target_row_offset]
84
+ elif nozzle_map.starting_nozzle == "A12":
85
+ if (target_column_index - target_column_offset >= 0) and (
86
+ target_row_index + target_row_offset
87
+ < len(target_wells_by_column[target_column_index])
88
+ ):
89
+ yield target_wells_by_column[
90
+ target_column_index - target_column_offset
91
+ ][target_row_index + target_row_offset]
92
+ elif nozzle_map.starting_nozzle == "H1":
93
+ if (
94
+ target_column_index + target_column_offset
95
+ < len(target_wells_by_column)
96
+ ) and (target_row_index - target_row_offset >= 0):
97
+ yield target_wells_by_column[
98
+ target_column_index + target_column_offset
99
+ ][target_row_index - target_row_offset]
100
+ elif nozzle_map.starting_nozzle == "H12":
101
+ if (target_column_index - target_column_offset >= 0) and (
102
+ target_row_index - target_row_offset >= 0
103
+ ):
104
+ yield target_wells_by_column[
105
+ target_column_index - target_column_offset
106
+ ][target_row_index - target_row_offset]
107
+ else:
108
+ raise InvalidProtocolData(
109
+ f"A pipette nozzle configuration may not having a starting nozzle of {nozzle_map.starting_nozzle}"
110
+ )
111
+
112
+
113
+ def wells_covered_sparse( # noqa: C901
114
+ nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]]
115
+ ) -> Iterator[str]:
116
+ """Get the list of wells covered by a nozzle map on a column-oriented reservoir.
117
+
118
+ This function handles reservoirs whose wells span multiple rows and columns - the most common case is something like a
119
+ 12-well reservoir, whose wells are the height of an SBS column and the width of an SBS row, or a 1-well reservoir whose well
120
+ is the size of an SBS active area.
121
+ """
122
+ target_row_index, target_column_index = row_col_ordinals_from_column_major_map(
123
+ target_well, target_wells_by_column
124
+ )
125
+ column_upsample = 12 // len(target_wells_by_column)
126
+ row_upsample = 8 // len(target_wells_by_column[0])
127
+ if column_upsample < 1 or row_upsample < 1:
128
+ raise InvalidStoredData(
129
+ "This labware cannot be used with wells_covered_sparse because it is more dense than an SBS 96 standard."
130
+ )
131
+ for nozzle_column in range(max(1, len(nozzle_map.columns) // column_upsample)):
132
+ for nozzle_row in range(max(1, len(nozzle_map.rows) // row_upsample)):
133
+ if nozzle_map.starting_nozzle == "A1":
134
+ if (
135
+ target_column_index + nozzle_column < len(target_wells_by_column)
136
+ ) and (
137
+ target_row_index + nozzle_row
138
+ < len(target_wells_by_column[target_column_index])
139
+ ):
140
+ yield target_wells_by_column[target_column_index + nozzle_column][
141
+ target_row_index + nozzle_row
142
+ ]
143
+ elif nozzle_map.starting_nozzle == "A12":
144
+ if (target_column_index - nozzle_column >= 0) and (
145
+ target_row_index + nozzle_row
146
+ < len(target_wells_by_column[target_column_index])
147
+ ):
148
+ yield target_wells_by_column[target_column_index - nozzle_column][
149
+ target_row_index + nozzle_row
150
+ ]
151
+ elif nozzle_map.starting_nozzle == "H1":
152
+ if (
153
+ target_column_index + nozzle_column
154
+ < len(target_wells_by_column[target_column_index])
155
+ ) and (target_row_index - nozzle_row >= 0):
156
+ yield target_wells_by_column[target_column_index + nozzle_column][
157
+ target_row_index - nozzle_row
158
+ ]
159
+ elif nozzle_map.starting_nozzle == "H12":
160
+ if (target_column_index - nozzle_column >= 0) and (
161
+ target_row_index - nozzle_row >= 0
162
+ ):
163
+ yield target_wells_by_column[target_column_index - nozzle_column][
164
+ target_row_index - nozzle_row
165
+ ]
166
+ else:
167
+ raise InvalidProtocolData(
168
+ f"A pipette nozzle configuration may not having a starting nozzle of {nozzle_map.starting_nozzle}"
169
+ )
170
+
171
+
172
+ def nozzles_per_well(
173
+ nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]]
174
+ ) -> int:
175
+ """Get the number of nozzles that will interact with each well in the labware.
176
+
177
+ For instance, if this is an SBS 96 or more dense, there is always 1 nozzle per well
178
+ that is interacted with (and some wells may not be interacted with at all). If this is
179
+ a 12-column reservoir, then all active nozzles in each column of the configuration will
180
+ interact with each well; so an 8-channel full config would have 8 nozzles per well,
181
+ and a 96 channel with a rectangle config from A1 to D12 would have 4 nozzles per well.
182
+ """
183
+ _, target_column_index = row_col_ordinals_from_column_major_map(
184
+ target_well, target_wells_by_column
185
+ )
186
+ # labware as or more dense than a 96 plate will only ever have 1 nozzle per well (and some wells won't be touched)
187
+ if len(target_wells_by_column) >= len(nozzle_map.columns) and len(
188
+ target_wells_by_column[target_column_index]
189
+ ) >= len(nozzle_map.rows):
190
+ return 1
191
+ return max(1, len(nozzle_map.columns) // len(target_wells_by_column)) * max(
192
+ 1, len(nozzle_map.rows) // len(target_wells_by_column[target_column_index])
193
+ )