opentrons 8.7.0a9__py3-none-any.whl → 8.8.0a7__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 (189) 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 +85 -17
  45. opentrons/hardware_control/poller.py +22 -8
  46. opentrons/hardware_control/protocols/liquid_handler.py +6 -2
  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/protocol.py +51 -2
  65. opentrons/protocol_api/core/engine/stringify.py +2 -0
  66. opentrons/protocol_api/core/engine/tasks.py +48 -0
  67. opentrons/protocol_api/core/engine/well.py +8 -0
  68. opentrons/protocol_api/core/instrument.py +19 -2
  69. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
  70. opentrons/protocol_api/core/legacy/legacy_module_core.py +33 -2
  71. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +23 -1
  72. opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
  73. opentrons/protocol_api/core/legacy/tasks.py +19 -0
  74. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +19 -2
  75. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
  76. opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
  77. opentrons/protocol_api/core/module.py +58 -2
  78. opentrons/protocol_api/core/protocol.py +23 -2
  79. opentrons/protocol_api/core/tasks.py +31 -0
  80. opentrons/protocol_api/core/well.py +4 -0
  81. opentrons/protocol_api/instrument_context.py +388 -2
  82. opentrons/protocol_api/labware.py +10 -2
  83. opentrons/protocol_api/module_contexts.py +170 -6
  84. opentrons/protocol_api/protocol_context.py +87 -21
  85. opentrons/protocol_api/robot_context.py +41 -25
  86. opentrons/protocol_api/tasks.py +48 -0
  87. opentrons/protocol_api/validation.py +49 -3
  88. opentrons/protocol_engine/__init__.py +4 -0
  89. opentrons/protocol_engine/actions/__init__.py +6 -2
  90. opentrons/protocol_engine/actions/actions.py +31 -9
  91. opentrons/protocol_engine/clients/sync_client.py +42 -7
  92. opentrons/protocol_engine/commands/__init__.py +56 -0
  93. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
  94. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
  95. opentrons/protocol_engine/commands/absorbance_reader/read.py +22 -23
  96. opentrons/protocol_engine/commands/aspirate.py +1 -0
  97. opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
  98. opentrons/protocol_engine/commands/capture_image.py +302 -0
  99. opentrons/protocol_engine/commands/command.py +2 -0
  100. opentrons/protocol_engine/commands/command_unions.py +62 -0
  101. opentrons/protocol_engine/commands/create_timer.py +83 -0
  102. opentrons/protocol_engine/commands/dispense.py +1 -0
  103. opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
  104. opentrons/protocol_engine/commands/drop_tip.py +32 -8
  105. opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
  106. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
  107. opentrons/protocol_engine/commands/heater_shaker/__init__.py +14 -0
  108. opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
  109. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
  110. opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
  111. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
  112. opentrons/protocol_engine/commands/move_labware.py +3 -4
  113. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
  114. opentrons/protocol_engine/commands/movement_common.py +31 -2
  115. opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
  116. opentrons/protocol_engine/commands/pipetting_common.py +48 -3
  117. opentrons/protocol_engine/commands/set_tip_state.py +97 -0
  118. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +38 -7
  119. opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -0
  120. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
  121. opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
  122. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +44 -7
  123. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +43 -14
  124. opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
  125. opentrons/protocol_engine/commands/touch_tip.py +1 -1
  126. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
  127. opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
  128. opentrons/protocol_engine/create_protocol_engine.py +12 -0
  129. opentrons/protocol_engine/engine_support.py +3 -0
  130. opentrons/protocol_engine/errors/__init__.py +12 -0
  131. opentrons/protocol_engine/errors/exceptions.py +119 -0
  132. opentrons/protocol_engine/execution/__init__.py +4 -0
  133. opentrons/protocol_engine/execution/command_executor.py +62 -1
  134. opentrons/protocol_engine/execution/create_queue_worker.py +9 -2
  135. opentrons/protocol_engine/execution/labware_movement.py +13 -15
  136. opentrons/protocol_engine/execution/movement.py +2 -0
  137. opentrons/protocol_engine/execution/pipetting.py +19 -25
  138. opentrons/protocol_engine/execution/queue_worker.py +4 -0
  139. opentrons/protocol_engine/execution/run_control.py +8 -0
  140. opentrons/protocol_engine/execution/task_handler.py +157 -0
  141. opentrons/protocol_engine/protocol_engine.py +137 -36
  142. opentrons/protocol_engine/resources/__init__.py +4 -0
  143. opentrons/protocol_engine/resources/camera_provider.py +110 -0
  144. opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
  145. opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
  146. opentrons/protocol_engine/resources/file_provider.py +133 -58
  147. opentrons/protocol_engine/resources/labware_validation.py +10 -6
  148. opentrons/protocol_engine/slot_standardization.py +2 -0
  149. opentrons/protocol_engine/state/_well_math.py +60 -18
  150. opentrons/protocol_engine/state/addressable_areas.py +2 -0
  151. opentrons/protocol_engine/state/camera.py +54 -0
  152. opentrons/protocol_engine/state/commands.py +37 -14
  153. opentrons/protocol_engine/state/geometry.py +276 -379
  154. opentrons/protocol_engine/state/labware.py +62 -108
  155. opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
  156. opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1336 -0
  157. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
  158. opentrons/protocol_engine/state/modules.py +30 -8
  159. opentrons/protocol_engine/state/motion.py +44 -0
  160. opentrons/protocol_engine/state/preconditions.py +59 -0
  161. opentrons/protocol_engine/state/state.py +44 -0
  162. opentrons/protocol_engine/state/state_summary.py +4 -0
  163. opentrons/protocol_engine/state/tasks.py +139 -0
  164. opentrons/protocol_engine/state/tips.py +177 -258
  165. opentrons/protocol_engine/state/update_types.py +26 -9
  166. opentrons/protocol_engine/types/__init__.py +23 -4
  167. opentrons/protocol_engine/types/command_preconditions.py +18 -0
  168. opentrons/protocol_engine/types/deck_configuration.py +5 -1
  169. opentrons/protocol_engine/types/instrument.py +8 -1
  170. opentrons/protocol_engine/types/labware.py +1 -13
  171. opentrons/protocol_engine/types/location.py +26 -2
  172. opentrons/protocol_engine/types/module.py +11 -1
  173. opentrons/protocol_engine/types/tasks.py +38 -0
  174. opentrons/protocol_engine/types/tip.py +9 -0
  175. opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
  176. opentrons/protocol_runner/protocol_runner.py +14 -1
  177. opentrons/protocol_runner/run_orchestrator.py +49 -2
  178. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
  179. opentrons/protocols/api_support/definitions.py +1 -1
  180. opentrons/protocols/api_support/types.py +2 -1
  181. opentrons/simulate.py +51 -15
  182. opentrons/system/camera.py +334 -4
  183. opentrons/system/ffmpeg.py +110 -0
  184. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/METADATA +4 -4
  185. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/RECORD +188 -160
  186. opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
  187. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/WHEEL +0 -0
  188. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/entry_points.txt +0 -0
  189. {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a7.dist-info}/licenses/LICENSE +0 -0
@@ -1,636 +0,0 @@
1
- """Utilities for calculating the labware origin offset position."""
2
- import dataclasses
3
- from typing import Union, overload
4
-
5
- from typing_extensions import assert_type
6
-
7
- from opentrons.types import Point
8
- from opentrons_shared_data.labware.labware_definition import (
9
- LabwareDefinition,
10
- LabwareDefinition2,
11
- LabwareDefinition3,
12
- Extents,
13
- AxisAlignedBoundingBox3D,
14
- Vector3D,
15
- )
16
- from opentrons_shared_data.labware.types import (
17
- SlotFootprintAsChildFeature,
18
- LocatingFeatures,
19
- SpringDirectionalForce,
20
- SlotFootprintAsParentFeature,
21
- )
22
- from opentrons.protocol_engine.types import AddressableArea
23
- from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3
24
- from ..types import (
25
- LabwareParentDefinition,
26
- ModuleDefinition,
27
- ModuleModel,
28
- DeckLocationDefinition,
29
- LabwareLocation,
30
- ModuleLocation,
31
- DeckSlotLocation,
32
- AddressableAreaLocation,
33
- OnLabwareLocation,
34
- )
35
-
36
- _OFFSET_ON_TC_OT2 = Point(x=0, y=0, z=10.7)
37
-
38
-
39
- @dataclasses.dataclass
40
- class _Labware3SupportedParentDefinition:
41
- features: LocatingFeatures
42
- extents: Extents
43
-
44
-
45
- @overload
46
- def get_parent_placement_origin_to_lw_origin(
47
- child_labware: LabwareDefinition,
48
- parent_deck_item: ModuleDefinition,
49
- module_parent_to_child_offset: Point,
50
- deck_definition: DeckDefinitionV5,
51
- is_topmost_labware: bool,
52
- labware_location: ModuleLocation,
53
- ) -> Point:
54
- ...
55
-
56
-
57
- @overload
58
- def get_parent_placement_origin_to_lw_origin(
59
- child_labware: LabwareDefinition,
60
- parent_deck_item: DeckLocationDefinition,
61
- module_parent_to_child_offset: None,
62
- deck_definition: DeckDefinitionV5,
63
- is_topmost_labware: bool,
64
- labware_location: Union[DeckSlotLocation, AddressableAreaLocation],
65
- ) -> Point:
66
- ...
67
-
68
-
69
- @overload
70
- def get_parent_placement_origin_to_lw_origin(
71
- child_labware: LabwareDefinition,
72
- parent_deck_item: LabwareDefinition,
73
- module_parent_to_child_offset: None,
74
- deck_definition: DeckDefinitionV5,
75
- is_topmost_labware: bool,
76
- labware_location: OnLabwareLocation,
77
- ) -> Point:
78
- ...
79
-
80
-
81
- def get_parent_placement_origin_to_lw_origin(
82
- child_labware: LabwareDefinition,
83
- parent_deck_item: LabwareParentDefinition,
84
- module_parent_to_child_offset: Union[Point, None],
85
- deck_definition: DeckDefinitionV5,
86
- is_topmost_labware: bool,
87
- labware_location: LabwareLocation,
88
- ) -> Point:
89
- """Returns the offset from parent entity's placement origin to child labware origin.
90
-
91
- Placement origin varies depending on the parent entity type (labware v3 are the back left bottom, and
92
- labware v2, modules, & deck location types are the front left bottom).
93
-
94
- Only parent-child specific offsets are calculated. Offsets that apply to a single entity
95
- (ex., module cal) or the entire stackup (ex., LPC) are handled elsewhere.
96
- """
97
- if isinstance(child_labware, LabwareDefinition2):
98
- parent_deck_item_origin_to_child_labware_placement_origin = (
99
- _get_parent_deck_item_origin_to_child_labware_placement_origin(
100
- child_labware=child_labware,
101
- parent_deck_item=parent_deck_item,
102
- module_parent_to_child_offset=module_parent_to_child_offset,
103
- deck_definition=deck_definition,
104
- labware_location=labware_location,
105
- )
106
- )
107
-
108
- # For v2 definitions, cornerOffsetFromSlot is the parent entity placement origin to child labware origin offset.
109
- # For compatibility with historical (buggy?) behavior,
110
- # we only consider it when the child labware is the topmost labware in a stackup.
111
- parent_deck_item_to_child_labware_offset = (
112
- Point.from_xyz_attrs(child_labware.cornerOffsetFromSlot)
113
- if is_topmost_labware
114
- else Point(0, 0, 0)
115
- )
116
-
117
- return (
118
- parent_deck_item_origin_to_child_labware_placement_origin
119
- + parent_deck_item_to_child_labware_offset
120
- )
121
- else:
122
- # For v3 definitions, get the vector from the back left bottom to the front right bottom.
123
- assert_type(child_labware, LabwareDefinition3)
124
-
125
- if isinstance(parent_deck_item, LabwareDefinition2):
126
- raise NotImplementedError()
127
-
128
- # TODO(jh, 06-25-25): This code is entirely temporary and only exists for the purposes of more useful
129
- # snapshot testing. This code should exist in NO capacity after features are implemented outside of the
130
- # module_parent_to_child_offset.
131
- if _shim_does_locating_feature_pair_exist(
132
- child_labware=child_labware,
133
- parent_deck_item=_get_standardized_parent_deck_item(parent_deck_item),
134
- ):
135
- parent_deck_item_origin_to_child_labware_placement_origin = (
136
- _module_parent_to_child_offset(
137
- module_parent_to_child_offset, labware_location
138
- )
139
- )
140
- else:
141
- parent_deck_item_origin_to_child_labware_placement_origin = (
142
- _get_parent_deck_item_origin_to_child_labware_placement_origin(
143
- child_labware=child_labware,
144
- parent_deck_item=parent_deck_item,
145
- module_parent_to_child_offset=module_parent_to_child_offset,
146
- deck_definition=deck_definition,
147
- labware_location=labware_location,
148
- )
149
- )
150
-
151
- parent_deck_item_to_child_labware_feature_offset = (
152
- _parent_deck_item_to_child_labware_feature_offset(
153
- child_labware=child_labware,
154
- parent_deck_item=_get_standardized_parent_deck_item(parent_deck_item),
155
- )
156
- ) + _feature_exception_offsets(
157
- deck_definition=deck_definition, parent_deck_item=parent_deck_item
158
- )
159
-
160
- return (
161
- parent_deck_item_origin_to_child_labware_placement_origin
162
- + parent_deck_item_to_child_labware_feature_offset
163
- )
164
-
165
-
166
- def _get_parent_deck_item_origin_to_child_labware_placement_origin(
167
- child_labware: LabwareDefinition,
168
- parent_deck_item: LabwareParentDefinition,
169
- module_parent_to_child_offset: Union[Point, None],
170
- deck_definition: DeckDefinitionV5,
171
- labware_location: LabwareLocation,
172
- ) -> Point:
173
- """Get the offset vector from parent entity origin to child labware placement origin."""
174
- if isinstance(labware_location, (DeckSlotLocation, AddressableAreaLocation)):
175
- return Point(x=0, y=0, z=0)
176
-
177
- elif isinstance(labware_location, ModuleLocation):
178
- assert isinstance(parent_deck_item, ModuleDefinition)
179
-
180
- child_labware_overlap_with_parent_deck_item = (
181
- _get_child_labware_overlap_with_parent_module(
182
- child_labware=child_labware,
183
- parent_module_model=parent_deck_item.model,
184
- deck_definition=deck_definition,
185
- )
186
- )
187
- module_parent_to_child_offset = _module_parent_to_child_offset(
188
- module_parent_to_child_offset, labware_location
189
- )
190
-
191
- return (
192
- module_parent_to_child_offset - child_labware_overlap_with_parent_deck_item
193
- )
194
-
195
- elif isinstance(labware_location, OnLabwareLocation):
196
- assert isinstance(parent_deck_item, (LabwareDefinition2, LabwareDefinition3))
197
-
198
- # TODO(jh, 06-05-25): This logic is slightly duplicative of LabwareView get_dimensions. Can we unify?
199
- if isinstance(parent_deck_item, LabwareDefinition2):
200
- parent_deck_item_height = parent_deck_item.dimensions.zDimension
201
- else:
202
- assert_type(parent_deck_item, LabwareDefinition3)
203
- parent_deck_item_height = (
204
- parent_deck_item.extents.total.frontRightTop.z
205
- - parent_deck_item.extents.total.backLeftBottom.z
206
- )
207
-
208
- child_labware_overlap_with_parent_deck_item = (
209
- _get_child_labware_overlap_with_parent_labware(
210
- child_labware=child_labware,
211
- parent_labware_name=parent_deck_item.parameters.loadName,
212
- )
213
- )
214
-
215
- return Point(
216
- x=child_labware_overlap_with_parent_deck_item.x,
217
- y=child_labware_overlap_with_parent_deck_item.y,
218
- z=parent_deck_item_height - child_labware_overlap_with_parent_deck_item.z,
219
- )
220
-
221
- else:
222
- raise TypeError(f"Unsupported labware location type: {labware_location}")
223
-
224
-
225
- def _module_parent_to_child_offset(
226
- module_parent_to_child_offset: Union[Point, None],
227
- labware_location: LabwareLocation,
228
- ) -> Point:
229
- """Returns the module offset if applicable."""
230
- if (
231
- isinstance(labware_location, ModuleLocation)
232
- and module_parent_to_child_offset is not None
233
- ):
234
- return Point.from_xyz_attrs(module_parent_to_child_offset)
235
- else:
236
- return Point(0, 0, 0)
237
-
238
-
239
- def _shim_does_locating_feature_pair_exist(
240
- child_labware: LabwareDefinition3,
241
- parent_deck_item: _Labware3SupportedParentDefinition,
242
- ) -> bool:
243
- """Temporary util."""
244
- slot_footprint_exists = (
245
- parent_deck_item.features.get("slotFootprintAsParent") is not None
246
- and child_labware.features.get("slotFootprintAsChild") is not None
247
- )
248
- flex_tiprack_lid_exists = (
249
- parent_deck_item.features.get("opentronsFlexTipRackLidAsParent") is not None
250
- and child_labware.features.get("opentronsFlexTipRackLidAsChild") is not None
251
- )
252
-
253
- return slot_footprint_exists or flex_tiprack_lid_exists
254
-
255
-
256
- def _get_standardized_parent_deck_item(
257
- parent_deck_item: Union[
258
- LabwareDefinition3, DeckLocationDefinition, ModuleDefinition
259
- ],
260
- ) -> _Labware3SupportedParentDefinition:
261
- """Returns a standardized parent deck item interface."""
262
- if isinstance(parent_deck_item, ModuleDefinition):
263
- slot_footprint_as_parent = _module_slot_footprint_as_parent(parent_deck_item)
264
- if slot_footprint_as_parent is not None:
265
- return _Labware3SupportedParentDefinition(
266
- features={
267
- **parent_deck_item.features,
268
- "slotFootprintAsParent": slot_footprint_as_parent,
269
- },
270
- extents=parent_deck_item.extents,
271
- )
272
- else:
273
- return _Labware3SupportedParentDefinition(
274
- features=parent_deck_item.features, extents=parent_deck_item.extents
275
- )
276
- elif isinstance(parent_deck_item, AddressableArea):
277
- extents = Extents(
278
- total=AxisAlignedBoundingBox3D(
279
- backLeftBottom=Vector3D(x=0, y=0, z=0),
280
- frontRightTop=Vector3D(
281
- x=parent_deck_item.bounding_box.x,
282
- y=parent_deck_item.bounding_box.y * 1,
283
- z=parent_deck_item.bounding_box.z,
284
- ),
285
- )
286
- )
287
-
288
- slot_footprint_as_parent = _aa_slot_footprint_as_parent(parent_deck_item)
289
- if slot_footprint_as_parent is not None:
290
- return _Labware3SupportedParentDefinition(
291
- features={
292
- **parent_deck_item.features,
293
- "slotFootprintAsParent": slot_footprint_as_parent,
294
- },
295
- extents=extents,
296
- )
297
- else:
298
- return _Labware3SupportedParentDefinition(
299
- parent_deck_item.features, extents=extents
300
- )
301
- elif isinstance(parent_deck_item, LabwareDefinition3):
302
- return _Labware3SupportedParentDefinition(
303
- features=parent_deck_item.features, extents=parent_deck_item.extents
304
- )
305
- # The slotDefV3 case.
306
- else:
307
- extents = Extents(
308
- total=AxisAlignedBoundingBox3D(
309
- backLeftBottom=Vector3D(x=0, y=0, z=0),
310
- frontRightTop=Vector3D(
311
- x=parent_deck_item["boundingBox"]["xDimension"],
312
- y=parent_deck_item["boundingBox"]["yDimension"] * 1,
313
- z=parent_deck_item["boundingBox"]["zDimension"],
314
- ),
315
- )
316
- )
317
- slot_footprint_as_parent = _slot_def_slot_footprint_as_parent(parent_deck_item)
318
- return _Labware3SupportedParentDefinition(
319
- features={
320
- **parent_deck_item["features"],
321
- "slotFootprintAsParent": slot_footprint_as_parent,
322
- },
323
- extents=extents,
324
- )
325
-
326
-
327
- def _module_slot_footprint_as_parent(
328
- parent_deck_item: ModuleDefinition,
329
- ) -> SlotFootprintAsParentFeature | None:
330
- """Returns the slot footprint as parent feature if inherently supported by the module definition.
331
-
332
- This utility is a normalization shim until labwareOffset + labwareInterfaceX/YDimension is deleted in module defs
333
- and replaced with the same slotFootprintAsParent that exists in labware def v3.
334
- """
335
- dimensions = parent_deck_item.dimensions
336
- if (
337
- dimensions.labwareInterfaceYDimension is None
338
- or dimensions.labwareInterfaceXDimension is None
339
- ):
340
- return None
341
- else:
342
- # Modules with springs would require special mating types and therefore are not handled here.
343
- return SlotFootprintAsParentFeature(
344
- z=0,
345
- backLeft={"x": 0, "y": dimensions.labwareInterfaceYDimension},
346
- frontRight={"x": dimensions.labwareInterfaceXDimension, "y": 0},
347
- )
348
-
349
-
350
- def _aa_slot_footprint_as_parent(
351
- parent_deck_item: AddressableArea,
352
- ) -> SlotFootprintAsParentFeature | None:
353
- """Returns the slot footprint as parent feature for addressable areas.
354
-
355
- This utility is a normalization shim until bounding box in deck defs and
356
- replaced with the same slotFootprintAsParent that exists in labware def v3.
357
- """
358
- bb = parent_deck_item.bounding_box
359
-
360
- if parent_deck_item.mating_surface_unit_vector is not None:
361
- if parent_deck_item.mating_surface_unit_vector == [-1, 1, -1]:
362
- return SlotFootprintAsParentFeature(
363
- z=0,
364
- backLeft={"x": 0, "y": bb.y},
365
- frontRight={"x": bb.x, "y": 0},
366
- springDirectionalForce="backLeftBottom",
367
- )
368
- else:
369
- raise NotImplementedError(
370
- "Slot footprint as parent does not support mating surface unit vector."
371
- )
372
- else:
373
- return SlotFootprintAsParentFeature(
374
- z=0,
375
- backLeft={"x": 0, "y": bb.y},
376
- frontRight={"x": bb.x, "y": 0},
377
- )
378
-
379
-
380
- def _slot_def_slot_footprint_as_parent(
381
- parent_deck_item: SlotDefV3,
382
- ) -> SlotFootprintAsParentFeature:
383
- """Returns the slot footprint as parent feature for slot definitions.
384
-
385
- This utility is a normalization shim until bounding box in deck defs and
386
- replaced with the same slotFootprintAsParent that exists in labware def v3.
387
- """
388
- bb = parent_deck_item["boundingBox"]
389
- return SlotFootprintAsParentFeature(
390
- z=0,
391
- backLeft={"x": 0, "y": bb["yDimension"]},
392
- frontRight={"x": bb["xDimension"], "y": 0},
393
- springDirectionalForce="backLeftBottom",
394
- )
395
-
396
-
397
- def _parent_deck_item_to_child_labware_feature_offset(
398
- child_labware: LabwareDefinition3,
399
- parent_deck_item: _Labware3SupportedParentDefinition,
400
- ) -> Point:
401
- """Get the offset vector from the parent entity origin to the child labware origin."""
402
- if (
403
- parent_deck_item.features.get("opentronsFlexTipRackLidAsParent") is not None
404
- and child_labware.features.get("opentronsFlexTipRackLidAsChild") is not None
405
- ):
406
- # TODO(jh, 07-29-25): Support center X/Y calculation after addressing grip point
407
- # calculations. See #18929 discussion.
408
- return _parent_origin_to_flex_tip_rack_lid_feature(
409
- parent_deck_item
410
- ) + _flex_tip_rack_lid_feature_to_child_origin(child_labware)
411
- elif (
412
- parent_deck_item.features.get("slotFootprintAsParent") is not None
413
- and child_labware.features.get("slotFootprintAsChild") is not None
414
- ):
415
- spring_force = _get_spring_force(child_labware, parent_deck_item)
416
-
417
- if spring_force is not None:
418
- if spring_force == "backLeftBottom":
419
- return _parent_origin_to_slot_back_left_bottom(
420
- parent_deck_item
421
- ) + _slot_back_left_bottom_to_child_origin(child_labware)
422
- else:
423
- raise NotImplementedError(f"Spring force: {spring_force}")
424
- else:
425
- return _parent_origin_to_slot_bottom_center(
426
- parent_deck_item
427
- ) + slot_bottom_center_to_child_origin(child_labware)
428
- else:
429
- # TODO(jh, 06-25-25): This is a temporary shim to unblock FE usage with LW Def3 and more accurately diff
430
- # ongoing positioning snapshot changes, but we should throw an error after adding all locating features
431
- # if no appropriate LF pair is found.
432
- return Point(0, 0, 0)
433
-
434
-
435
- def _get_spring_force(
436
- child_labware: LabwareDefinition3,
437
- parent_deck_item: _Labware3SupportedParentDefinition,
438
- ) -> SpringDirectionalForce | None:
439
- """Returns whether the parent-child stackup has a spring that affects positioning."""
440
- assert parent_deck_item.features.get("slotFootprintAsParent") is not None
441
- assert child_labware.features.get("slotFootprintAsChild") is not None
442
-
443
- parent_spring_force = parent_deck_item.features["slotFootprintAsParent"].get(
444
- "springDirectionalForce"
445
- )
446
- child_spring_force = child_labware.features["slotFootprintAsChild"].get(
447
- "springDirectionalForce"
448
- )
449
-
450
- if parent_spring_force is not None and child_spring_force is not None:
451
- if parent_spring_force != child_spring_force:
452
- raise ValueError(
453
- f"Parent spring force: {parent_spring_force} does not match child spring force: {child_spring_force}"
454
- )
455
-
456
- return parent_spring_force or child_spring_force
457
-
458
-
459
- def _parent_origin_to_flex_tip_rack_lid_feature(
460
- parent_deck_item: _Labware3SupportedParentDefinition,
461
- ) -> Point:
462
- """Returns the offset from a deck item's origin to the Flex tip rack lid locating feature."""
463
- flex_tip_rack_lid_as_parent = parent_deck_item.features.get(
464
- "opentronsFlexTipRackLidAsParent"
465
- )
466
- assert flex_tip_rack_lid_as_parent is not None
467
-
468
- return Point(x=0, y=0, z=flex_tip_rack_lid_as_parent["matingZ"])
469
-
470
-
471
- def _parent_origin_to_slot_bottom_center(
472
- parent_deck_item: _Labware3SupportedParentDefinition,
473
- ) -> Point:
474
- """Returns the offset from a deck item's origin to the bottom center of the slot that it provides."""
475
- slot_footprint_as_parent = parent_deck_item.features.get("slotFootprintAsParent")
476
- assert slot_footprint_as_parent is not None
477
-
478
- x = (
479
- slot_footprint_as_parent["frontRight"]["x"]
480
- + slot_footprint_as_parent["backLeft"]["x"]
481
- ) / 2
482
- y = (
483
- slot_footprint_as_parent["frontRight"]["y"]
484
- + slot_footprint_as_parent["backLeft"]["y"]
485
- ) / 2
486
- z = slot_footprint_as_parent["z"]
487
-
488
- return Point(x, y, z)
489
-
490
-
491
- def _parent_origin_to_slot_back_left_bottom(
492
- parent_deck_item: _Labware3SupportedParentDefinition,
493
- ) -> Point:
494
- """Returns the offset from a deck item's origin to the back left bottom of the slot that it provides."""
495
- slot_footprint_as_parent = parent_deck_item.features.get("slotFootprintAsParent")
496
- assert slot_footprint_as_parent is not None
497
-
498
- x = slot_footprint_as_parent["backLeft"]["x"]
499
- y = slot_footprint_as_parent["backLeft"]["y"]
500
- z = slot_footprint_as_parent["z"]
501
-
502
- return Point(x, y, z)
503
-
504
-
505
- def _flex_tip_rack_lid_feature_to_child_origin(
506
- child_labware: LabwareDefinition3,
507
- ) -> Point:
508
- """Returns the offset from a Flex tip rack lid locating feature to the child origin."""
509
- flex_tip_rack_lid_as_child = child_labware.features.get(
510
- "opentronsFlexTipRackLidAsChild"
511
- )
512
- assert flex_tip_rack_lid_as_child is not None
513
-
514
- return Point(x=0, y=0, z=flex_tip_rack_lid_as_child["matingZ"])
515
-
516
-
517
- def slot_bottom_center_to_child_origin(
518
- child_labware: LabwareDefinition3,
519
- ) -> Point:
520
- """Returns offset from a parent slot's bottom center to the child origin."""
521
- slot_footprint_as_child = child_labware.features.get("slotFootprintAsChild")
522
- assert slot_footprint_as_child is not None
523
-
524
- x = (
525
- slot_footprint_as_child["frontRight"]["x"]
526
- + slot_footprint_as_child["backLeft"]["x"]
527
- ) / 2
528
- y = (
529
- slot_footprint_as_child["frontRight"]["y"]
530
- + slot_footprint_as_child["backLeft"]["y"]
531
- ) / 2
532
- z = slot_footprint_as_child["z"]
533
-
534
- return Point(x, y, z) * -1
535
-
536
-
537
- def _slot_back_left_bottom_to_child_origin(
538
- child_labware: LabwareDefinition3,
539
- ) -> Point:
540
- """Returns offset from a parent slot's back left bottom to the child's origin."""
541
- slot_footprint_as_child = child_labware.features.get("slotFootprintAsChild")
542
- assert slot_footprint_as_child is not None
543
-
544
- x = slot_footprint_as_child["backLeft"]["x"]
545
- y = slot_footprint_as_child["backLeft"]["y"]
546
- z = slot_footprint_as_child["z"]
547
-
548
- return Point(x, y, z) * -1
549
-
550
-
551
- def _child_back_left_bottom_position(child_labware: LabwareDefinition3) -> Point:
552
- """Get the back left bottom position from a v3 labware definition."""
553
- footprint_as_child = _get_labware_footprint_as_child(child_labware)
554
-
555
- return Point(
556
- x=footprint_as_child["backLeft"]["x"],
557
- y=footprint_as_child["frontRight"]["y"],
558
- z=footprint_as_child["z"],
559
- )
560
-
561
-
562
- def _get_child_labware_overlap_with_parent_labware(
563
- child_labware: LabwareDefinition, parent_labware_name: str
564
- ) -> Point:
565
- """Get the child labware's overlap with the parent labware's load name."""
566
- overlap = child_labware.stackingOffsetWithLabware.get(parent_labware_name)
567
-
568
- if overlap is None:
569
- overlap = child_labware.stackingOffsetWithLabware.get("default")
570
-
571
- if overlap is None:
572
- raise ValueError(
573
- f"No default labware overlap specified for parent labware: {parent_labware_name}"
574
- )
575
- else:
576
- return Point.from_xyz_attrs(overlap)
577
-
578
-
579
- def _get_child_labware_overlap_with_parent_module(
580
- child_labware: LabwareDefinition,
581
- parent_module_model: ModuleModel,
582
- deck_definition: DeckDefinitionV5,
583
- ) -> Point:
584
- """Get the child labware's overlap with the parent module model."""
585
- child_labware_overlap = child_labware.stackingOffsetWithModule.get(
586
- str(parent_module_model.value)
587
- )
588
- if not child_labware_overlap:
589
- if _is_thermocycler_on_ot2(parent_module_model, deck_definition):
590
- return _OFFSET_ON_TC_OT2
591
- else:
592
- return Point(x=0, y=0, z=0)
593
-
594
- return Point.from_xyz_attrs(child_labware_overlap)
595
-
596
-
597
- def _feature_exception_offsets(
598
- parent_deck_item: LabwareParentDefinition,
599
- deck_definition: DeckDefinitionV5,
600
- ) -> Point:
601
- """These offsets are intended for legacy reasons only and should generally be avoided post labware schema 2.
602
-
603
- If you need to make exceptions for a parent-child stackup, use the `custom` locating feature.
604
- """
605
- if isinstance(parent_deck_item, ModuleDefinition) and _is_thermocycler_on_ot2(
606
- parent_deck_item.model, deck_definition
607
- ):
608
- return _OFFSET_ON_TC_OT2
609
- else:
610
- return Point(x=0, y=0, z=0)
611
-
612
-
613
- def _is_thermocycler_on_ot2(
614
- parent_module_model: ModuleModel,
615
- deck_definition: DeckDefinitionV5,
616
- ) -> bool:
617
- """Whether the given parent module is a thermocycler with the current deck being an OT2 deck."""
618
- robot_model = deck_definition["robot"]["model"]
619
- return (
620
- parent_module_model
621
- in [ModuleModel.THERMOCYCLER_MODULE_V1, ModuleModel.THERMOCYCLER_MODULE_V2]
622
- and robot_model == "OT-2 Standard"
623
- )
624
-
625
-
626
- def _get_labware_footprint_as_child(
627
- labware: LabwareDefinition3,
628
- ) -> SlotFootprintAsChildFeature:
629
- """Get the SlotFootprintAsChildFeature for labware definitions."""
630
- footprint_as_child = labware.features.get("slotFootprintAsChild")
631
- if footprint_as_child is None:
632
- raise ValueError(
633
- f"Expected labware {labware.metadata.displayName} to have a SlotFootprintAsChild feature"
634
- )
635
- else:
636
- return footprint_as_child