opentrons 8.7.0a9__py3-none-any.whl → 8.8.0a8__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 (190) hide show
  1. opentrons/_version.py +2 -2
  2. opentrons/cli/analyze.py +4 -1
  3. opentrons/config/__init__.py +7 -0
  4. opentrons/drivers/asyncio/communication/serial_connection.py +126 -49
  5. opentrons/drivers/heater_shaker/abstract.py +5 -0
  6. opentrons/drivers/heater_shaker/driver.py +10 -0
  7. opentrons/drivers/heater_shaker/simulator.py +4 -0
  8. opentrons/drivers/thermocycler/abstract.py +6 -0
  9. opentrons/drivers/thermocycler/driver.py +61 -10
  10. opentrons/drivers/thermocycler/simulator.py +6 -0
  11. opentrons/drivers/vacuum_module/__init__.py +5 -0
  12. opentrons/drivers/vacuum_module/abstract.py +93 -0
  13. opentrons/drivers/vacuum_module/driver.py +208 -0
  14. opentrons/drivers/vacuum_module/errors.py +39 -0
  15. opentrons/drivers/vacuum_module/simulator.py +85 -0
  16. opentrons/drivers/vacuum_module/types.py +79 -0
  17. opentrons/execute.py +3 -0
  18. opentrons/hardware_control/api.py +24 -5
  19. opentrons/hardware_control/backends/controller.py +8 -2
  20. opentrons/hardware_control/backends/flex_protocol.py +1 -0
  21. opentrons/hardware_control/backends/ot3controller.py +35 -2
  22. opentrons/hardware_control/backends/ot3simulator.py +3 -1
  23. opentrons/hardware_control/backends/ot3utils.py +37 -0
  24. opentrons/hardware_control/backends/simulator.py +2 -1
  25. opentrons/hardware_control/backends/subsystem_manager.py +5 -2
  26. opentrons/hardware_control/emulation/abstract_emulator.py +6 -4
  27. opentrons/hardware_control/emulation/connection_handler.py +8 -5
  28. opentrons/hardware_control/emulation/heater_shaker.py +12 -3
  29. opentrons/hardware_control/emulation/settings.py +1 -1
  30. opentrons/hardware_control/emulation/thermocycler.py +67 -15
  31. opentrons/hardware_control/module_control.py +105 -10
  32. opentrons/hardware_control/modules/__init__.py +3 -0
  33. opentrons/hardware_control/modules/absorbance_reader.py +11 -4
  34. opentrons/hardware_control/modules/flex_stacker.py +38 -9
  35. opentrons/hardware_control/modules/heater_shaker.py +42 -5
  36. opentrons/hardware_control/modules/magdeck.py +8 -4
  37. opentrons/hardware_control/modules/mod_abc.py +14 -6
  38. opentrons/hardware_control/modules/tempdeck.py +25 -5
  39. opentrons/hardware_control/modules/thermocycler.py +68 -11
  40. opentrons/hardware_control/modules/types.py +20 -1
  41. opentrons/hardware_control/modules/utils.py +11 -4
  42. opentrons/hardware_control/motion_utilities.py +6 -6
  43. opentrons/hardware_control/nozzle_manager.py +3 -0
  44. opentrons/hardware_control/ot3api.py +92 -17
  45. opentrons/hardware_control/poller.py +22 -8
  46. opentrons/hardware_control/protocols/liquid_handler.py +12 -4
  47. opentrons/hardware_control/scripts/update_module_fw.py +5 -0
  48. opentrons/hardware_control/types.py +43 -2
  49. opentrons/legacy_commands/commands.py +58 -5
  50. opentrons/legacy_commands/module_commands.py +52 -0
  51. opentrons/legacy_commands/protocol_commands.py +53 -1
  52. opentrons/legacy_commands/types.py +155 -1
  53. opentrons/motion_planning/deck_conflict.py +17 -12
  54. opentrons/motion_planning/waypoints.py +15 -29
  55. opentrons/protocol_api/__init__.py +5 -1
  56. opentrons/protocol_api/_transfer_liquid_validation.py +17 -2
  57. opentrons/protocol_api/_types.py +8 -1
  58. opentrons/protocol_api/core/common.py +3 -1
  59. opentrons/protocol_api/core/engine/_default_labware_versions.py +33 -11
  60. opentrons/protocol_api/core/engine/deck_conflict.py +3 -1
  61. opentrons/protocol_api/core/engine/instrument.py +109 -26
  62. opentrons/protocol_api/core/engine/labware.py +8 -1
  63. opentrons/protocol_api/core/engine/module_core.py +95 -4
  64. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +4 -18
  65. opentrons/protocol_api/core/engine/protocol.py +51 -2
  66. opentrons/protocol_api/core/engine/stringify.py +2 -0
  67. opentrons/protocol_api/core/engine/tasks.py +48 -0
  68. opentrons/protocol_api/core/engine/well.py +8 -0
  69. opentrons/protocol_api/core/instrument.py +19 -2
  70. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
  71. opentrons/protocol_api/core/legacy/legacy_module_core.py +33 -2
  72. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +23 -1
  73. opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
  74. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  75. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
  76. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  77. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  78. opentrons/protocol_api/core/module.py +58 -2
  79. opentrons/protocol_api/core/protocol.py +23 -2
  80. opentrons/protocol_api/core/tasks.py +31 -0
  81. opentrons/protocol_api/core/well.py +4 -0
  82. opentrons/protocol_api/instrument_context.py +388 -2
  83. opentrons/protocol_api/labware.py +10 -2
  84. opentrons/protocol_api/module_contexts.py +170 -6
  85. opentrons/protocol_api/protocol_context.py +87 -21
  86. opentrons/protocol_api/robot_context.py +41 -25
  87. opentrons/protocol_api/tasks.py +48 -0
  88. opentrons/protocol_api/validation.py +49 -3
  89. opentrons/protocol_engine/__init__.py +4 -0
  90. opentrons/protocol_engine/actions/__init__.py +6 -2
  91. opentrons/protocol_engine/actions/actions.py +31 -9
  92. opentrons/protocol_engine/clients/sync_client.py +42 -7
  93. opentrons/protocol_engine/commands/__init__.py +56 -0
  94. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  95. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  96. opentrons/protocol_engine/commands/absorbance_reader/read.py +22 -23
  97. opentrons/protocol_engine/commands/aspirate.py +1 -0
  98. opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
  99. opentrons/protocol_engine/commands/capture_image.py +302 -0
  100. opentrons/protocol_engine/commands/command.py +2 -0
  101. opentrons/protocol_engine/commands/command_unions.py +62 -0
  102. opentrons/protocol_engine/commands/create_timer.py +83 -0
  103. opentrons/protocol_engine/commands/dispense.py +1 -0
  104. opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
  105. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  106. opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
  107. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
  108. opentrons/protocol_engine/commands/heater_shaker/__init__.py +14 -0
  109. opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
  110. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
  111. opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
  112. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
  113. opentrons/protocol_engine/commands/move_labware.py +3 -4
  114. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
  115. opentrons/protocol_engine/commands/movement_common.py +31 -2
  116. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  117. opentrons/protocol_engine/commands/pipetting_common.py +48 -3
  118. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  119. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +38 -7
  120. opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -0
  121. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  122. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  123. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +44 -7
  124. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +43 -14
  125. opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
  126. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  127. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  128. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  129. opentrons/protocol_engine/create_protocol_engine.py +12 -0
  130. opentrons/protocol_engine/engine_support.py +3 -0
  131. opentrons/protocol_engine/errors/__init__.py +12 -0
  132. opentrons/protocol_engine/errors/exceptions.py +119 -0
  133. opentrons/protocol_engine/execution/__init__.py +4 -0
  134. opentrons/protocol_engine/execution/command_executor.py +62 -1
  135. opentrons/protocol_engine/execution/create_queue_worker.py +9 -2
  136. opentrons/protocol_engine/execution/labware_movement.py +13 -15
  137. opentrons/protocol_engine/execution/movement.py +2 -0
  138. opentrons/protocol_engine/execution/pipetting.py +26 -25
  139. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  140. opentrons/protocol_engine/execution/run_control.py +8 -0
  141. opentrons/protocol_engine/execution/task_handler.py +157 -0
  142. opentrons/protocol_engine/protocol_engine.py +137 -36
  143. opentrons/protocol_engine/resources/__init__.py +4 -0
  144. opentrons/protocol_engine/resources/camera_provider.py +110 -0
  145. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  146. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  147. opentrons/protocol_engine/resources/file_provider.py +133 -58
  148. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  149. opentrons/protocol_engine/slot_standardization.py +2 -0
  150. opentrons/protocol_engine/state/_well_math.py +60 -18
  151. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  152. opentrons/protocol_engine/state/camera.py +54 -0
  153. opentrons/protocol_engine/state/commands.py +37 -14
  154. opentrons/protocol_engine/state/geometry.py +276 -379
  155. opentrons/protocol_engine/state/labware.py +62 -108
  156. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  157. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1336 -0
  158. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  159. opentrons/protocol_engine/state/modules.py +30 -8
  160. opentrons/protocol_engine/state/motion.py +60 -18
  161. opentrons/protocol_engine/state/preconditions.py +59 -0
  162. opentrons/protocol_engine/state/state.py +44 -0
  163. opentrons/protocol_engine/state/state_summary.py +4 -0
  164. opentrons/protocol_engine/state/tasks.py +139 -0
  165. opentrons/protocol_engine/state/tips.py +177 -258
  166. opentrons/protocol_engine/state/update_types.py +26 -9
  167. opentrons/protocol_engine/types/__init__.py +23 -4
  168. opentrons/protocol_engine/types/command_preconditions.py +18 -0
  169. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  170. opentrons/protocol_engine/types/instrument.py +8 -1
  171. opentrons/protocol_engine/types/labware.py +1 -13
  172. opentrons/protocol_engine/types/location.py +26 -2
  173. opentrons/protocol_engine/types/module.py +11 -1
  174. opentrons/protocol_engine/types/tasks.py +38 -0
  175. opentrons/protocol_engine/types/tip.py +9 -0
  176. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  177. opentrons/protocol_runner/protocol_runner.py +14 -1
  178. opentrons/protocol_runner/run_orchestrator.py +49 -2
  179. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
  180. opentrons/protocols/api_support/definitions.py +1 -1
  181. opentrons/protocols/api_support/types.py +2 -1
  182. opentrons/simulate.py +51 -15
  183. opentrons/system/camera.py +334 -4
  184. opentrons/system/ffmpeg.py +110 -0
  185. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/METADATA +4 -4
  186. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/RECORD +189 -161
  187. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  188. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/WHEEL +0 -0
  189. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/entry_points.txt +0 -0
  190. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/licenses/LICENSE +0 -0
@@ -52,11 +52,11 @@ from ..types import (
52
52
  LabwareOffsetLocationSequence,
53
53
  LegacyLabwareOffsetLocation,
54
54
  InStackerHopperLocation,
55
+ WASTE_CHUTE_LOCATION,
55
56
  LabwareLocation,
56
57
  LoadedLabware,
57
58
  ModuleLocation,
58
59
  OverlapOffset,
59
- LabwareMovementOffsetData,
60
60
  OnDeckLabwareLocation,
61
61
  OFF_DECK_LOCATION,
62
62
  )
@@ -344,14 +344,18 @@ class LabwareStore(HasState[LabwareState], HandlesActions):
344
344
  self, labware_id: str, new_location: LabwareLocation, new_offset_id: str | None
345
345
  ) -> None:
346
346
  self._state.labware_by_id[labware_id].offsetId = new_offset_id
347
-
348
347
  if isinstance(new_location, AddressableAreaLocation) and (
349
- fixture_validation.is_gripper_waste_chute(new_location.addressableAreaName)
350
- or fixture_validation.is_trash(new_location.addressableAreaName)
348
+ fixture_validation.is_trash(new_location.addressableAreaName)
351
349
  ):
352
- # If a labware has been moved into a waste chute it's been chuted away and is now technically off deck
350
+ # TODO (RC, 2025-10-07: create a specific trash off deck location)
351
+ # If a labware has been moved into trash and is now technically off deck
353
352
  new_location = OFF_DECK_LOCATION
354
-
353
+ elif isinstance(
354
+ new_location, AddressableAreaLocation
355
+ ) and fixture_validation.is_gripper_waste_chute(
356
+ new_location.addressableAreaName
357
+ ):
358
+ new_location = WASTE_CHUTE_LOCATION
355
359
  self._state.labware_by_id[labware_id].location = new_location
356
360
 
357
361
  def _set_labware_location(self, state_update: update_types.StateUpdate) -> None:
@@ -401,7 +405,7 @@ class LabwareView:
401
405
  return self._state.labware_by_id[labware_id]
402
406
  except KeyError as e:
403
407
  raise errors.LabwareNotLoadedError(
404
- f"Labware {labware_id} not found."
408
+ f"Labware with id {labware_id} not found."
405
409
  ) from e
406
410
 
407
411
  def known(self, labware_id: str) -> bool:
@@ -430,7 +434,7 @@ class LabwareView:
430
434
  ):
431
435
  return labware.id
432
436
  raise errors.exceptions.LabwareNotLoadedOnLabwareError(
433
- f"There is not labware loaded onto labware {labware_id}"
437
+ f"There is not labware loaded onto labware {self.get_display_name(labware_id)}"
434
438
  )
435
439
 
436
440
  def raise_if_labware_has_non_lid_labware_on_top(self, labware_id: str) -> None:
@@ -443,7 +447,8 @@ class LabwareView:
443
447
  and candidate_id != lid_id
444
448
  ):
445
449
  raise errors.LabwareIsInStackError(
446
- f"Cannot access labware {labware_id} because it has a non-lid labware stacked on top."
450
+ f"Cannot access labware {self.get_display_name(labware_id)} because it has"
451
+ " a non-lid labware stacked on top."
447
452
  )
448
453
 
449
454
  def raise_if_labware_has_labware_on_top(self, labware_id: str) -> None:
@@ -454,16 +459,35 @@ class LabwareView:
454
459
  and labware.location.labwareId == labware_id
455
460
  ):
456
461
  raise errors.LabwareIsInStackError(
457
- f"Cannot access labware {labware_id} because it has another labware stacked on top."
462
+ f"Cannot access labware {self.get_display_name(labware_id)} because it has"
463
+ " another labware stacked on top."
458
464
  )
459
465
 
466
+ def raise_if_not_tip_rack(self, labware_id: str) -> None:
467
+ """Raise if a labware is not a tip rack."""
468
+ if not self.is_tiprack(labware_id):
469
+ raise errors.LabwareIsNotTipRackError(
470
+ f"Labware {self.get_display_name(labware_id)} is not a tip rack and cannot have its well states set."
471
+ )
472
+
473
+ def raise_if_wells_are_invalid(
474
+ self, labware_id: str, well_names: List[str]
475
+ ) -> None:
476
+ """Raise if given wells do not exist with the given labware ID."""
477
+ non_existent_wells = set(well_names) - set(
478
+ self.get_definition(labware_id).wells
479
+ )
480
+ if non_existent_wells:
481
+ raise errors.WellDoesNotExistError(
482
+ f"Tip rack {self.get_display_name(labware_id)} does not have wells: {', '.join(non_existent_wells)}"
483
+ )
484
+
460
485
  def get_by_slot(
461
486
  self,
462
487
  slot_name: Union[DeckSlotName, StagingSlotName],
463
488
  ) -> Optional[LoadedLabware]:
464
489
  """Get the labware located in a given slot, if any."""
465
490
  loaded_labware = list(self._state.labware_by_id.values())
466
-
467
491
  for labware in loaded_labware:
468
492
  if (
469
493
  isinstance(labware.location, DeckSlotLocation)
@@ -663,6 +687,14 @@ class LabwareView:
663
687
  or len(self.get_definition(labware_id).wells) >= 96
664
688
  )
665
689
 
690
+ def get_has_96_subwells(self, labware_id: str) -> bool:
691
+ """True if a labware is a reservoir with a 96-grid of sub-wells."""
692
+ return self.get_has_quirk(labware_id, "offsetPipetteFor96GridSubwells")
693
+
694
+ def get_has_12_subwells(self, labware_id: str) -> bool:
695
+ """True if a labware is a reservoir with a 12-grid of sub-wells."""
696
+ return self.get_has_quirk(labware_id, "offsetPipetteFor12GridSubwells")
697
+
666
698
  def get_well_definition(
667
699
  self,
668
700
  labware_id: str,
@@ -681,7 +713,7 @@ class LabwareView:
681
713
  return definition.wells[well_name]
682
714
  except KeyError as e:
683
715
  raise errors.WellDoesNotExistError(
684
- f"{well_name} does not exist in {labware_id}."
716
+ f"{well_name} does not exist in {self.get_display_name(labware_id)}."
685
717
  ) from e
686
718
 
687
719
  def get_well_geometry(
@@ -691,19 +723,21 @@ class LabwareView:
691
723
  labware_def = self.get_definition(labware_id)
692
724
  if labware_def.innerLabwareGeometry is None:
693
725
  raise errors.IncompleteLabwareDefinitionError(
694
- message=f"No innerLabwareGeometry found in labware definition for labware_id: {labware_id}."
726
+ message=f"No innerLabwareGeometry found in labware definition for {self.get_display_name(labware_id)}."
695
727
  )
696
728
  well_def = self.get_well_definition(labware_id, well_name)
697
729
  geometry_id = well_def.geometryDefinitionId
698
730
  if geometry_id is None:
699
731
  raise errors.IncompleteWellDefinitionError(
700
- message=f"No geometryDefinitionId found in well definition for well: {well_name} in labware_id: {labware_id}"
732
+ message=f"No geometryDefinitionId found in well definition for well {well_name}"
733
+ f" for {self.get_display_name(labware_id)}"
701
734
  )
702
735
  else:
703
736
  well_geometry = labware_def.innerLabwareGeometry.get(geometry_id)
704
737
  if well_geometry is None:
705
738
  raise errors.IncompleteLabwareDefinitionError(
706
- message=f"No innerLabwareGeometry found in labware definition for well_id: {geometry_id} in labware_id: {labware_id}"
739
+ message=f"No innerLabwareGeometry found in labware definition for geometry id {geometry_id}"
740
+ f" for {self.get_display_name(labware_id)}"
707
741
  )
708
742
  return well_geometry
709
743
 
@@ -772,15 +806,15 @@ class LabwareView:
772
806
  contains_wells = all(well_name in labware_wells for well_name in iter(wells))
773
807
  if labware_definition.parameters.isTiprack:
774
808
  raise errors.LabwareIsTipRackError(
775
- f"Given labware: {labware_id} is a tiprack. Can not load liquid."
809
+ f"Given labware {self.get_display_name(labware_id)} is a tip rack. Can not load liquid."
776
810
  )
777
811
  if LabwareRole.adapter in labware_definition.allowedRoles:
778
812
  raise errors.LabwareIsAdapterError(
779
- f"Given labware: {labware_id} is an adapter. Can not load liquid."
813
+ f"Given labware {self.get_display_name(labware_id)} is an adapter. Can not load liquid."
780
814
  )
781
815
  if not contains_wells:
782
816
  raise errors.WellDoesNotExistError(
783
- f"Some of the supplied wells do not match the labwareId: {labware_id}."
817
+ f"Some of the supplied wells do not match the labware {self.get_display_name(labware_id)}."
784
818
  )
785
819
  return list(wells)
786
820
 
@@ -789,7 +823,7 @@ class LabwareView:
789
823
  definition = self.get_definition(labware_id)
790
824
  if definition.parameters.tipLength is None:
791
825
  raise errors.LabwareIsNotTipRackError(
792
- f"Labware {labware_id} has no tip length defined."
826
+ f"Labware {self.get_display_name(labware_id)} has no tip length defined."
793
827
  )
794
828
 
795
829
  return definition.parameters.tipLength - overlap
@@ -1095,7 +1129,9 @@ class LabwareView:
1095
1129
  raise errors.LabwareCannotBeStackedError(
1096
1130
  f"Labware {lid_labware_definition.parameters.loadName} cannot be used as a lid in the Flex Stacker."
1097
1131
  )
1098
- if not labware_validation.validate_labware_can_be_stacked(
1132
+ if isinstance(
1133
+ lid_labware_definition, LabwareDefinition2
1134
+ ) and not labware_validation.validate_legacy_labware_can_be_stacked(
1099
1135
  lid_labware_definition, primary_labware_definition.parameters.loadName
1100
1136
  ):
1101
1137
  raise errors.LabwareCannotBeStackedError(
@@ -1108,7 +1144,9 @@ class LabwareView:
1108
1144
  raise errors.LabwareCannotBeStackedError(
1109
1145
  f"Labware {adapter_labware_definition.parameters.loadName} cannot be used as an adapter in the Flex Stacker."
1110
1146
  )
1111
- if not labware_validation.validate_labware_can_be_stacked(
1147
+ if isinstance(
1148
+ primary_labware_definition, LabwareDefinition2
1149
+ ) and not labware_validation.validate_legacy_labware_can_be_stacked(
1112
1150
  primary_labware_definition,
1113
1151
  adapter_labware_definition.parameters.loadName,
1114
1152
  ):
@@ -1162,9 +1200,9 @@ class LabwareView:
1162
1200
  below_labware = self.get(bottom_labware_id)
1163
1201
  if isinstance(
1164
1202
  top_labware_definition, LabwareDefinition2
1165
- ) and not labware_validation.validate_labware_can_be_stacked(
1166
- top_labware_definition=top_labware_definition,
1167
- below_labware_load_name=below_labware.loadName,
1203
+ ) and not labware_validation.validate_legacy_labware_can_be_stacked(
1204
+ child_labware_definition=top_labware_definition,
1205
+ parent_labware_load_name=below_labware.loadName,
1168
1206
  ):
1169
1207
  raise errors.LabwareCannotBeStackedError(
1170
1208
  f"Labware {top_labware_definition.parameters.loadName} cannot be loaded onto labware {below_labware.loadName}"
@@ -1225,28 +1263,6 @@ class LabwareView:
1225
1263
  uri = self.get_uri_from_definition(self.get_definition(labware_id))
1226
1264
  return uri in _MAGDECK_HALF_MM_LABWARE
1227
1265
 
1228
- def get_deck_default_gripper_offsets(self) -> Optional[LabwareMovementOffsetData]:
1229
- """Get the deck's default gripper offsets."""
1230
- parsed_offsets = (
1231
- self.get_deck_definition().get("gripperOffsets", {}).get("default")
1232
- )
1233
- return (
1234
- LabwareMovementOffsetData(
1235
- pickUpOffset=LabwareOffsetVector(
1236
- x=parsed_offsets["pickUpOffset"]["x"],
1237
- y=parsed_offsets["pickUpOffset"]["y"],
1238
- z=parsed_offsets["pickUpOffset"]["z"],
1239
- ),
1240
- dropOffset=LabwareOffsetVector(
1241
- x=parsed_offsets["dropOffset"]["x"],
1242
- y=parsed_offsets["dropOffset"]["y"],
1243
- z=parsed_offsets["dropOffset"]["z"],
1244
- ),
1245
- )
1246
- if parsed_offsets
1247
- else None
1248
- )
1249
-
1250
1266
  def get_absorbance_reader_lid_definition(self) -> LabwareDefinition:
1251
1267
  """Return the special labware definition for the plate reader lid.
1252
1268
 
@@ -1257,68 +1273,6 @@ class LabwareView:
1257
1273
  "opentrons/opentrons_flex_lid_absorbance_plate_reader_module/1"
1258
1274
  ]
1259
1275
 
1260
- @overload
1261
- def get_child_gripper_offsets(
1262
- self,
1263
- *,
1264
- labware_definition: LabwareDefinition,
1265
- slot_name: Optional[DeckSlotName],
1266
- ) -> Optional[LabwareMovementOffsetData]:
1267
- pass
1268
-
1269
- @overload
1270
- def get_child_gripper_offsets(
1271
- self, *, labware_id: str, slot_name: Optional[DeckSlotName]
1272
- ) -> Optional[LabwareMovementOffsetData]:
1273
- pass
1274
-
1275
- def get_child_gripper_offsets(
1276
- self,
1277
- *,
1278
- labware_definition: Optional[LabwareDefinition] = None,
1279
- labware_id: Optional[str] = None,
1280
- slot_name: Optional[DeckSlotName],
1281
- ) -> Optional[LabwareMovementOffsetData]:
1282
- """Get the grip offsets that a labware says should be applied to children stacked atop it.
1283
-
1284
- Params:
1285
- labware_id: The ID of a parent labware (atop which another labware, the child, will be stacked).
1286
- slot_name: The ancestor slot that the parent labware is ultimately loaded into,
1287
- perhaps after going through a module in the middle.
1288
-
1289
- Returns:
1290
- If `slot_name` is provided, returns the gripper offsets that the parent labware definition
1291
- specifies just for that slot, or `None` if the labware definition doesn't have an
1292
- exact match.
1293
-
1294
- If `slot_name` is `None`, returns the gripper offsets that the parent labware
1295
- definition designates as "default," or `None` if it doesn't designate any as such.
1296
- """
1297
- if labware_id is not None:
1298
- labware_definition = self.get_definition(labware_id)
1299
- else:
1300
- # Should be ensured by our @overloads.
1301
- assert labware_definition is not None
1302
-
1303
- parsed_offsets = labware_definition.gripperOffsets
1304
- offset_key = slot_name.id if slot_name else "default"
1305
-
1306
- if parsed_offsets is None or offset_key not in parsed_offsets:
1307
- return None
1308
- else:
1309
- return LabwareMovementOffsetData(
1310
- pickUpOffset=LabwareOffsetVector.model_construct(
1311
- x=parsed_offsets[offset_key].pickUpOffset.x,
1312
- y=parsed_offsets[offset_key].pickUpOffset.y,
1313
- z=parsed_offsets[offset_key].pickUpOffset.z,
1314
- ),
1315
- dropOffset=LabwareOffsetVector.model_construct(
1316
- x=parsed_offsets[offset_key].dropOffset.x,
1317
- y=parsed_offsets[offset_key].dropOffset.y,
1318
- z=parsed_offsets[offset_key].dropOffset.z,
1319
- ),
1320
- )
1321
-
1322
1276
  def get_grip_force(self, labware_definition: LabwareDefinition) -> float:
1323
1277
  """Get the recommended grip force for gripping labware using gripper."""
1324
1278
  recommended_force = labware_definition.gripForce
@@ -0,0 +1,94 @@
1
+ """Labware origin math errors."""
2
+
3
+ from typing import Any, Dict, Optional, Sequence
4
+
5
+ from opentrons.protocol_engine.errors import ProtocolEngineError
6
+ from opentrons_shared_data.errors import ErrorCodes
7
+ from opentrons_shared_data.errors.exceptions import EnumeratedError
8
+
9
+
10
+ class LabwareLocatingFeatureError(ProtocolEngineError):
11
+ """Base class for errors related to labware locating features."""
12
+
13
+ def __init__(
14
+ self,
15
+ message: Optional[str] = None,
16
+ details: Optional[Dict[str, Any]] = None,
17
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
18
+ ) -> None:
19
+ """Build a LabwareLocatingFeatureError."""
20
+ super().__init__(
21
+ ErrorCodes.LABWARE_LOCATING_FEATURE_ERROR, message, details, wrapping
22
+ )
23
+
24
+
25
+ class MissingLocatingFeatureError(LabwareLocatingFeatureError):
26
+ """Raised when a labware definition is missing a required locating feature."""
27
+
28
+ def __init__(
29
+ self,
30
+ labware_name: str,
31
+ required_feature: str,
32
+ message: Optional[str] = None,
33
+ details: Optional[Dict[str, Any]] = None,
34
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
35
+ ) -> None:
36
+ """Build a MissingLocatingFeatureError."""
37
+ if message is None:
38
+ message = f"Expected {labware_name} to have {required_feature} feature"
39
+
40
+ if details is None:
41
+ details = {
42
+ "labware_name": labware_name,
43
+ "required_feature": required_feature,
44
+ }
45
+
46
+ super().__init__(message, details, wrapping)
47
+
48
+
49
+ class InvalidLabwarePlacementError(LabwareLocatingFeatureError):
50
+ """Raised when a labware cannot be placed in the specified location due to locating feature constraints."""
51
+
52
+ def __init__(
53
+ self,
54
+ feature_name: str,
55
+ invalid_placement: str,
56
+ message: Optional[str] = None,
57
+ details: Optional[Dict[str, Any]] = None,
58
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
59
+ ) -> None:
60
+ """Build an InvalidLabwarePlacementError."""
61
+ if message is None:
62
+ message = f"{feature_name} feature does not support placement: {invalid_placement}"
63
+
64
+ if details is None:
65
+ details = {
66
+ "feature_name": feature_name,
67
+ "invalid_placement": invalid_placement,
68
+ }
69
+
70
+ super().__init__(message, details, wrapping)
71
+
72
+
73
+ class IncompatibleLocatingFeatureError(LabwareLocatingFeatureError):
74
+ """Raised when parent and child labware have incompatible locating features."""
75
+
76
+ def __init__(
77
+ self,
78
+ parent_feature: str,
79
+ child_feature: str,
80
+ message: Optional[str] = None,
81
+ details: Optional[Dict[str, Any]] = None,
82
+ wrapping: Optional[Sequence[EnumeratedError]] = None,
83
+ ) -> None:
84
+ """Build an IncompatibleLocatingFeatureError."""
85
+ if message is None:
86
+ message = f"Incompatible labware features: parent {parent_feature}, child {child_feature}"
87
+
88
+ if details is None:
89
+ details = {
90
+ "parent_feature": parent_feature,
91
+ "child_feature": child_feature,
92
+ }
93
+
94
+ super().__init__(message, details, wrapping)