opentrons 8.4.1a2__py2.py3-none-any.whl → 8.5.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 (67) hide show
  1. opentrons/config/defaults_ot3.py +1 -1
  2. opentrons/hardware_control/backends/flex_protocol.py +25 -0
  3. opentrons/hardware_control/backends/ot3controller.py +76 -1
  4. opentrons/hardware_control/backends/ot3simulator.py +27 -0
  5. opentrons/hardware_control/instruments/ot3/pipette_handler.py +1 -0
  6. opentrons/hardware_control/ot3api.py +32 -0
  7. opentrons/legacy_commands/commands.py +16 -4
  8. opentrons/legacy_commands/robot_commands.py +51 -0
  9. opentrons/legacy_commands/types.py +91 -2
  10. opentrons/protocol_api/_liquid.py +60 -15
  11. opentrons/protocol_api/_liquid_properties.py +149 -90
  12. opentrons/protocol_api/_transfer_liquid_validation.py +43 -14
  13. opentrons/protocol_api/core/engine/instrument.py +367 -221
  14. opentrons/protocol_api/core/engine/protocol.py +14 -15
  15. opentrons/protocol_api/core/engine/robot.py +2 -2
  16. opentrons/protocol_api/core/engine/transfer_components_executor.py +275 -163
  17. opentrons/protocol_api/core/engine/well.py +16 -0
  18. opentrons/protocol_api/core/instrument.py +11 -5
  19. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +11 -5
  20. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +2 -2
  21. opentrons/protocol_api/core/legacy/legacy_well_core.py +8 -0
  22. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +11 -5
  23. opentrons/protocol_api/core/protocol.py +3 -3
  24. opentrons/protocol_api/core/well.py +8 -0
  25. opentrons/protocol_api/instrument_context.py +478 -111
  26. opentrons/protocol_api/labware.py +10 -0
  27. opentrons/protocol_api/module_contexts.py +5 -2
  28. opentrons/protocol_api/protocol_context.py +76 -11
  29. opentrons/protocol_api/robot_context.py +48 -6
  30. opentrons/protocol_api/validation.py +15 -8
  31. opentrons/protocol_engine/commands/command_unions.py +10 -10
  32. opentrons/protocol_engine/commands/generate_command_schema.py +1 -1
  33. opentrons/protocol_engine/commands/get_next_tip.py +2 -2
  34. opentrons/protocol_engine/commands/load_labware.py +0 -19
  35. opentrons/protocol_engine/commands/pick_up_tip.py +9 -3
  36. opentrons/protocol_engine/commands/robot/__init__.py +20 -20
  37. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +34 -24
  38. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +29 -20
  39. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +1 -1
  40. opentrons/protocol_engine/commands/unsafe/__init__.py +17 -1
  41. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +1 -2
  42. opentrons/protocol_engine/execution/labware_movement.py +9 -2
  43. opentrons/protocol_engine/execution/movement.py +12 -9
  44. opentrons/protocol_engine/execution/queue_worker.py +8 -1
  45. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +52 -19
  46. opentrons/protocol_engine/resources/labware_validation.py +7 -1
  47. opentrons/protocol_engine/state/_well_math.py +2 -2
  48. opentrons/protocol_engine/state/commands.py +14 -28
  49. opentrons/protocol_engine/state/frustum_helpers.py +11 -7
  50. opentrons/protocol_engine/state/labware.py +12 -0
  51. opentrons/protocol_engine/state/modules.py +1 -1
  52. opentrons/protocol_engine/state/pipettes.py +8 -0
  53. opentrons/protocol_engine/state/tips.py +46 -83
  54. opentrons/protocol_engine/state/update_types.py +8 -23
  55. opentrons/protocol_engine/types/liquid_level_detection.py +68 -8
  56. opentrons/protocol_runner/legacy_command_mapper.py +12 -6
  57. opentrons/protocol_runner/run_orchestrator.py +1 -1
  58. opentrons/protocols/advanced_control/transfers/common.py +54 -11
  59. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +55 -28
  60. opentrons/protocols/api_support/definitions.py +1 -1
  61. opentrons/types.py +6 -6
  62. {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/METADATA +4 -4
  63. {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/RECORD +67 -66
  64. {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/LICENSE +0 -0
  65. {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/WHEEL +0 -0
  66. {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/entry_points.txt +0 -0
  67. {opentrons-8.4.1a2.dist-info → opentrons-8.5.0.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,13 @@
1
1
  from dataclasses import dataclass
2
2
  from numpy import interp
3
- from typing import Optional, Dict, Sequence, Tuple, List
3
+ from typing import Optional, Dict, Sequence, Tuple, List, Union
4
4
 
5
5
  from opentrons_shared_data.liquid_classes.liquid_class_definition import (
6
+ TransferProperties as SharedDataTransferProperties,
6
7
  AspirateProperties as SharedDataAspirateProperties,
7
8
  SingleDispenseProperties as SharedDataSingleDispenseProperties,
8
9
  MultiDispenseProperties as SharedDataMultiDispenseProperties,
10
+ TipPosition as SharedDataTipPosition,
9
11
  DelayProperties as SharedDataDelayProperties,
10
12
  DelayParams as SharedDataDelayParams,
11
13
  TouchTipProperties as SharedDataTouchTipProperties,
@@ -22,12 +24,12 @@ from opentrons_shared_data.liquid_classes.liquid_class_definition import (
22
24
  PositionReference,
23
25
  Coordinate,
24
26
  )
25
-
26
27
  from . import validation
27
28
 
28
29
 
29
30
  class LiquidHandlingPropertyByVolume:
30
31
  def __init__(self, by_volume_property: Sequence[Tuple[float, float]]) -> None:
32
+ self._initial_properties_by_volume = by_volume_property
31
33
  self._properties_by_volume: Dict[float, float] = {
32
34
  float(volume): value for volume, value in by_volume_property
33
35
  }
@@ -60,6 +62,11 @@ class LiquidHandlingPropertyByVolume:
60
62
  interp(validated_volume, self._sorted_volumes, self._sorted_values)
61
63
  )
62
64
 
65
+ def set_for_all_volumes(self, value: float) -> None:
66
+ """Override all existing volume-dependent values with the given value."""
67
+ self.clear_values()
68
+ self.set_for_volume(0, value)
69
+
63
70
  def set_for_volume(self, volume: float, value: float) -> None:
64
71
  """Add a new volume and value for the property for the interpolation curve."""
65
72
  validated_volume = validation.ensure_positive_float(volume)
@@ -74,6 +81,17 @@ class LiquidHandlingPropertyByVolume:
74
81
  raise KeyError(f"No value set for volume {volume} uL")
75
82
  self._sort_volume_and_values()
76
83
 
84
+ def clear_values(self) -> None:
85
+ """Removes all existing volume and value pairs from the curve."""
86
+ self._properties_by_volume = {}
87
+
88
+ def reset_values(self) -> None:
89
+ """Resets volumes and values to the default."""
90
+ self._properties_by_volume = {
91
+ float(volume): value for volume, value in self._initial_properties_by_volume
92
+ }
93
+ self._sort_volume_and_values()
94
+
77
95
  def _sort_volume_and_values(self) -> None:
78
96
  """Sort volume in increasing order along with corresponding values in matching order."""
79
97
  self._sorted_volumes, self._sorted_values = (
@@ -86,6 +104,48 @@ class LiquidHandlingPropertyByVolume:
86
104
  # We use slots for this dataclass (and the rest of liquid properties) to prevent dynamic creation of attributes
87
105
  # not defined in the class, not for any performance reasons. This is so that mistyping properties when overriding
88
106
  # values will cause the protocol to fail analysis, rather than silently passing.
107
+ @dataclass(slots=True)
108
+ class TipPosition:
109
+
110
+ _position_reference: PositionReference
111
+ _offset: Coordinate
112
+
113
+ @property
114
+ def position_reference(self) -> PositionReference:
115
+ return self._position_reference
116
+
117
+ @position_reference.setter
118
+ def position_reference(self, new_position: Union[str, PositionReference]) -> None:
119
+ self._position_reference = (
120
+ new_position
121
+ if isinstance(new_position, PositionReference)
122
+ else PositionReference(new_position)
123
+ )
124
+
125
+ @property
126
+ def offset(self) -> Coordinate:
127
+ return self._offset
128
+
129
+ @offset.setter
130
+ def offset(self, new_offset: Union[Sequence[float], Coordinate]) -> None:
131
+ if isinstance(new_offset, Coordinate):
132
+ new_coordinate: Sequence[Union[int, float]] = [
133
+ new_offset.x,
134
+ new_offset.y,
135
+ new_offset.z,
136
+ ]
137
+ else:
138
+ new_coordinate = new_offset
139
+ x, y, z = validation.validate_coordinates(new_coordinate)
140
+ self._offset = Coordinate(x=x, y=y, z=z)
141
+
142
+ def as_shared_data_model(self) -> SharedDataTipPosition:
143
+ return SharedDataTipPosition(
144
+ positionReference=self._position_reference,
145
+ offset=self.offset,
146
+ )
147
+
148
+
89
149
  @dataclass(slots=True)
90
150
  class DelayProperties:
91
151
 
@@ -126,7 +186,7 @@ class TouchTipProperties:
126
186
 
127
187
  _enabled: bool
128
188
  _z_offset: Optional[float]
129
- _mm_to_edge: Optional[float]
189
+ _mm_from_edge: Optional[float]
130
190
  _speed: Optional[float]
131
191
 
132
192
  @property
@@ -137,10 +197,10 @@ class TouchTipProperties:
137
197
  def enabled(self, enable: bool) -> None:
138
198
  validated_enable = validation.ensure_boolean(enable)
139
199
  if validated_enable and (
140
- self._z_offset is None or self._mm_to_edge is None or self._speed is None
200
+ self._z_offset is None or self._mm_from_edge is None or self._speed is None
141
201
  ):
142
202
  raise ValueError(
143
- "z_offset, mm_to_edge and speed must be set before enabling touch tip."
203
+ "z_offset, mm_from_edge and speed must be set before enabling touch tip."
144
204
  )
145
205
  self._enabled = validated_enable
146
206
 
@@ -154,13 +214,13 @@ class TouchTipProperties:
154
214
  self._z_offset = validated_offset
155
215
 
156
216
  @property
157
- def mm_to_edge(self) -> Optional[float]:
158
- return self._mm_to_edge
217
+ def mm_from_edge(self) -> Optional[float]:
218
+ return self._mm_from_edge
159
219
 
160
- @mm_to_edge.setter
161
- def mm_to_edge(self, new_mm: float) -> None:
220
+ @mm_from_edge.setter
221
+ def mm_from_edge(self, new_mm: float) -> None:
162
222
  validated_mm = validation.ensure_float(new_mm)
163
- self._mm_to_edge = validated_mm
223
+ self._mm_from_edge = validated_mm
164
224
 
165
225
  @property
166
226
  def speed(self) -> Optional[float]:
@@ -175,12 +235,12 @@ class TouchTipProperties:
175
235
  """Get the touch tip params in schema v1 shape."""
176
236
  if (
177
237
  self._z_offset is not None
178
- and self._mm_to_edge is not None
238
+ and self._mm_from_edge is not None
179
239
  and self._speed is not None
180
240
  ):
181
241
  return SharedDataTouchTipParams(
182
242
  zOffset=self._z_offset,
183
- mmToEdge=self._mm_to_edge,
243
+ mmFromEdge=self._mm_from_edge,
184
244
  speed=self._speed,
185
245
  )
186
246
  else:
@@ -301,30 +361,11 @@ class BlowoutProperties:
301
361
 
302
362
 
303
363
  @dataclass(slots=True)
304
- class SubmergeRetractCommon:
364
+ class _SubmergeRetractCommon:
305
365
 
306
- _position_reference: PositionReference
307
- _offset: Coordinate
308
366
  _speed: float
309
367
  _delay: DelayProperties
310
368
 
311
- @property
312
- def position_reference(self) -> PositionReference:
313
- return self._position_reference
314
-
315
- @position_reference.setter
316
- def position_reference(self, new_position: str) -> None:
317
- self._position_reference = PositionReference(new_position)
318
-
319
- @property
320
- def offset(self) -> Coordinate:
321
- return self._offset
322
-
323
- @offset.setter
324
- def offset(self, new_offset: Sequence[float]) -> None:
325
- x, y, z = validation.validate_coordinates(new_offset)
326
- self._offset = Coordinate(x=x, y=y, z=z)
327
-
328
369
  @property
329
370
  def speed(self) -> float:
330
371
  return self._speed
@@ -340,22 +381,33 @@ class SubmergeRetractCommon:
340
381
 
341
382
 
342
383
  @dataclass(slots=True)
343
- class Submerge(SubmergeRetractCommon):
384
+ class Submerge(_SubmergeRetractCommon):
385
+
386
+ _start_position: TipPosition
387
+
388
+ @property
389
+ def start_position(self) -> TipPosition:
390
+ return self._start_position
391
+
344
392
  def as_shared_data_model(self) -> SharedDataSubmerge:
345
393
  return SharedDataSubmerge(
346
- positionReference=self._position_reference,
347
- offset=self._offset,
394
+ startPosition=self._start_position.as_shared_data_model(),
348
395
  speed=self._speed,
349
396
  delay=self._delay.as_shared_data_model(),
350
397
  )
351
398
 
352
399
 
353
400
  @dataclass(slots=True)
354
- class RetractAspirate(SubmergeRetractCommon):
401
+ class RetractAspirate(_SubmergeRetractCommon):
355
402
 
403
+ _end_position: TipPosition
356
404
  _air_gap_by_volume: LiquidHandlingPropertyByVolume
357
405
  _touch_tip: TouchTipProperties
358
406
 
407
+ @property
408
+ def end_position(self) -> TipPosition:
409
+ return self._end_position
410
+
359
411
  @property
360
412
  def air_gap_by_volume(self) -> LiquidHandlingPropertyByVolume:
361
413
  return self._air_gap_by_volume
@@ -366,8 +418,7 @@ class RetractAspirate(SubmergeRetractCommon):
366
418
 
367
419
  def as_shared_data_model(self) -> SharedDataRetractAspirate:
368
420
  return SharedDataRetractAspirate(
369
- positionReference=self._position_reference,
370
- offset=self._offset,
421
+ endPosition=self._end_position.as_shared_data_model(),
371
422
  speed=self._speed,
372
423
  airGapByVolume=self._air_gap_by_volume.as_list_of_tuples(),
373
424
  touchTip=self._touch_tip.as_shared_data_model(),
@@ -376,12 +427,16 @@ class RetractAspirate(SubmergeRetractCommon):
376
427
 
377
428
 
378
429
  @dataclass(slots=True)
379
- class RetractDispense(SubmergeRetractCommon):
380
-
430
+ class RetractDispense(_SubmergeRetractCommon):
431
+ _end_position: TipPosition
381
432
  _air_gap_by_volume: LiquidHandlingPropertyByVolume
382
433
  _touch_tip: TouchTipProperties
383
434
  _blowout: BlowoutProperties
384
435
 
436
+ @property
437
+ def end_position(self) -> TipPosition:
438
+ return self._end_position
439
+
385
440
  @property
386
441
  def air_gap_by_volume(self) -> LiquidHandlingPropertyByVolume:
387
442
  return self._air_gap_by_volume
@@ -396,8 +451,7 @@ class RetractDispense(SubmergeRetractCommon):
396
451
 
397
452
  def as_shared_data_model(self) -> SharedDataRetractDispense:
398
453
  return SharedDataRetractDispense(
399
- positionReference=self._position_reference,
400
- offset=self._offset,
454
+ endPosition=self._end_position.as_shared_data_model(),
401
455
  speed=self._speed,
402
456
  airGapByVolume=self._air_gap_by_volume.as_list_of_tuples(),
403
457
  blowout=self._blowout.as_shared_data_model(),
@@ -407,11 +461,9 @@ class RetractDispense(SubmergeRetractCommon):
407
461
 
408
462
 
409
463
  @dataclass(slots=True)
410
- class BaseLiquidHandlingProperties:
464
+ class _BaseLiquidHandlingProperties:
411
465
 
412
466
  _submerge: Submerge
413
- _position_reference: PositionReference
414
- _offset: Coordinate
415
467
  _flow_rate_by_volume: LiquidHandlingPropertyByVolume
416
468
  _correction_by_volume: LiquidHandlingPropertyByVolume
417
469
  _delay: DelayProperties
@@ -420,23 +472,6 @@ class BaseLiquidHandlingProperties:
420
472
  def submerge(self) -> Submerge:
421
473
  return self._submerge
422
474
 
423
- @property
424
- def position_reference(self) -> PositionReference:
425
- return self._position_reference
426
-
427
- @position_reference.setter
428
- def position_reference(self, new_position: str) -> None:
429
- self._position_reference = PositionReference(new_position)
430
-
431
- @property
432
- def offset(self) -> Coordinate:
433
- return self._offset
434
-
435
- @offset.setter
436
- def offset(self, new_offset: Sequence[float]) -> None:
437
- x, y, z = validation.validate_coordinates(new_offset)
438
- self._offset = Coordinate(x=x, y=y, z=z)
439
-
440
475
  @property
441
476
  def flow_rate_by_volume(self) -> LiquidHandlingPropertyByVolume:
442
477
  return self._flow_rate_by_volume
@@ -451,12 +486,17 @@ class BaseLiquidHandlingProperties:
451
486
 
452
487
 
453
488
  @dataclass(slots=True)
454
- class AspirateProperties(BaseLiquidHandlingProperties):
489
+ class AspirateProperties(_BaseLiquidHandlingProperties):
455
490
 
491
+ _aspirate_position: TipPosition
456
492
  _retract: RetractAspirate
457
493
  _pre_wet: bool
458
494
  _mix: MixProperties
459
495
 
496
+ @property
497
+ def aspirate_position(self) -> TipPosition:
498
+ return self._aspirate_position
499
+
460
500
  @property
461
501
  def pre_wet(self) -> bool:
462
502
  return self._pre_wet
@@ -478,8 +518,7 @@ class AspirateProperties(BaseLiquidHandlingProperties):
478
518
  return SharedDataAspirateProperties(
479
519
  submerge=self._submerge.as_shared_data_model(),
480
520
  retract=self._retract.as_shared_data_model(),
481
- positionReference=self._position_reference,
482
- offset=self._offset,
521
+ aspiratePosition=self._aspirate_position.as_shared_data_model(),
483
522
  flowRateByVolume=self._flow_rate_by_volume.as_list_of_tuples(),
484
523
  preWet=self._pre_wet,
485
524
  mix=self._mix.as_shared_data_model(),
@@ -489,12 +528,17 @@ class AspirateProperties(BaseLiquidHandlingProperties):
489
528
 
490
529
 
491
530
  @dataclass(slots=True)
492
- class SingleDispenseProperties(BaseLiquidHandlingProperties):
531
+ class SingleDispenseProperties(_BaseLiquidHandlingProperties):
493
532
 
533
+ _dispense_position: TipPosition
494
534
  _retract: RetractDispense
495
535
  _push_out_by_volume: LiquidHandlingPropertyByVolume
496
536
  _mix: MixProperties
497
537
 
538
+ @property
539
+ def dispense_position(self) -> TipPosition:
540
+ return self._dispense_position
541
+
498
542
  @property
499
543
  def push_out_by_volume(self) -> LiquidHandlingPropertyByVolume:
500
544
  return self._push_out_by_volume
@@ -511,8 +555,7 @@ class SingleDispenseProperties(BaseLiquidHandlingProperties):
511
555
  return SharedDataSingleDispenseProperties(
512
556
  submerge=self._submerge.as_shared_data_model(),
513
557
  retract=self._retract.as_shared_data_model(),
514
- positionReference=self._position_reference,
515
- offset=self._offset,
558
+ dispensePosition=self._dispense_position.as_shared_data_model(),
516
559
  flowRateByVolume=self._flow_rate_by_volume.as_list_of_tuples(),
517
560
  mix=self._mix.as_shared_data_model(),
518
561
  pushOutByVolume=self._push_out_by_volume.as_list_of_tuples(),
@@ -522,12 +565,17 @@ class SingleDispenseProperties(BaseLiquidHandlingProperties):
522
565
 
523
566
 
524
567
  @dataclass(slots=True)
525
- class MultiDispenseProperties(BaseLiquidHandlingProperties):
568
+ class MultiDispenseProperties(_BaseLiquidHandlingProperties):
526
569
 
570
+ _dispense_position: TipPosition
527
571
  _retract: RetractDispense
528
572
  _conditioning_by_volume: LiquidHandlingPropertyByVolume
529
573
  _disposal_by_volume: LiquidHandlingPropertyByVolume
530
574
 
575
+ @property
576
+ def dispense_position(self) -> TipPosition:
577
+ return self._dispense_position
578
+
531
579
  @property
532
580
  def retract(self) -> RetractDispense:
533
581
  return self._retract
@@ -544,8 +592,7 @@ class MultiDispenseProperties(BaseLiquidHandlingProperties):
544
592
  return SharedDataMultiDispenseProperties(
545
593
  submerge=self._submerge.as_shared_data_model(),
546
594
  retract=self._retract.as_shared_data_model(),
547
- positionReference=self._position_reference,
548
- offset=self._offset,
595
+ dispensePosition=self._dispense_position.as_shared_data_model(),
549
596
  flowRateByVolume=self._flow_rate_by_volume.as_list_of_tuples(),
550
597
  conditioningByVolume=self._conditioning_by_volume.as_list_of_tuples(),
551
598
  disposalByVolume=self._disposal_by_volume.as_list_of_tuples(),
@@ -576,6 +623,12 @@ class TransferProperties:
576
623
  return self._multi_dispense
577
624
 
578
625
 
626
+ def _build_tip_position(tip_position: SharedDataTipPosition) -> TipPosition:
627
+ return TipPosition(
628
+ _position_reference=tip_position.positionReference, _offset=tip_position.offset
629
+ )
630
+
631
+
579
632
  def _build_delay_properties(
580
633
  delay_properties: SharedDataDelayProperties,
581
634
  ) -> DelayProperties:
@@ -591,16 +644,16 @@ def _build_touch_tip_properties(
591
644
  ) -> TouchTipProperties:
592
645
  if touch_tip_properties.params is not None:
593
646
  z_offset = touch_tip_properties.params.zOffset
594
- mm_to_edge = touch_tip_properties.params.mmToEdge
647
+ mm_from_edge = touch_tip_properties.params.mmFromEdge
595
648
  speed = touch_tip_properties.params.speed
596
649
  else:
597
650
  z_offset = None
598
- mm_to_edge = None
651
+ mm_from_edge = None
599
652
  speed = None
600
653
  return TouchTipProperties(
601
654
  _enabled=touch_tip_properties.enable,
602
655
  _z_offset=z_offset,
603
- _mm_to_edge=mm_to_edge,
656
+ _mm_from_edge=mm_from_edge,
604
657
  _speed=speed,
605
658
  )
606
659
 
@@ -637,8 +690,7 @@ def _build_submerge(
637
690
  submerge_properties: SharedDataSubmerge,
638
691
  ) -> Submerge:
639
692
  return Submerge(
640
- _position_reference=submerge_properties.positionReference,
641
- _offset=submerge_properties.offset,
693
+ _start_position=_build_tip_position(submerge_properties.startPosition),
642
694
  _speed=submerge_properties.speed,
643
695
  _delay=_build_delay_properties(submerge_properties.delay),
644
696
  )
@@ -648,8 +700,7 @@ def _build_retract_aspirate(
648
700
  retract_aspirate: SharedDataRetractAspirate,
649
701
  ) -> RetractAspirate:
650
702
  return RetractAspirate(
651
- _position_reference=retract_aspirate.positionReference,
652
- _offset=retract_aspirate.offset,
703
+ _end_position=_build_tip_position(retract_aspirate.endPosition),
653
704
  _speed=retract_aspirate.speed,
654
705
  _air_gap_by_volume=LiquidHandlingPropertyByVolume(
655
706
  retract_aspirate.airGapByVolume
@@ -663,8 +714,7 @@ def _build_retract_dispense(
663
714
  retract_dispense: SharedDataRetractDispense,
664
715
  ) -> RetractDispense:
665
716
  return RetractDispense(
666
- _position_reference=retract_dispense.positionReference,
667
- _offset=retract_dispense.offset,
717
+ _end_position=_build_tip_position(retract_dispense.endPosition),
668
718
  _speed=retract_dispense.speed,
669
719
  _air_gap_by_volume=LiquidHandlingPropertyByVolume(
670
720
  retract_dispense.airGapByVolume
@@ -681,8 +731,7 @@ def build_aspirate_properties(
681
731
  return AspirateProperties(
682
732
  _submerge=_build_submerge(aspirate_properties.submerge),
683
733
  _retract=_build_retract_aspirate(aspirate_properties.retract),
684
- _position_reference=aspirate_properties.positionReference,
685
- _offset=aspirate_properties.offset,
734
+ _aspirate_position=_build_tip_position(aspirate_properties.aspiratePosition),
686
735
  _flow_rate_by_volume=LiquidHandlingPropertyByVolume(
687
736
  aspirate_properties.flowRateByVolume
688
737
  ),
@@ -701,8 +750,9 @@ def build_single_dispense_properties(
701
750
  return SingleDispenseProperties(
702
751
  _submerge=_build_submerge(single_dispense_properties.submerge),
703
752
  _retract=_build_retract_dispense(single_dispense_properties.retract),
704
- _position_reference=single_dispense_properties.positionReference,
705
- _offset=single_dispense_properties.offset,
753
+ _dispense_position=_build_tip_position(
754
+ single_dispense_properties.dispensePosition
755
+ ),
706
756
  _flow_rate_by_volume=LiquidHandlingPropertyByVolume(
707
757
  single_dispense_properties.flowRateByVolume
708
758
  ),
@@ -725,8 +775,9 @@ def build_multi_dispense_properties(
725
775
  return MultiDispenseProperties(
726
776
  _submerge=_build_submerge(multi_dispense_properties.submerge),
727
777
  _retract=_build_retract_dispense(multi_dispense_properties.retract),
728
- _position_reference=multi_dispense_properties.positionReference,
729
- _offset=multi_dispense_properties.offset,
778
+ _dispense_position=_build_tip_position(
779
+ multi_dispense_properties.dispensePosition
780
+ ),
730
781
  _flow_rate_by_volume=LiquidHandlingPropertyByVolume(
731
782
  multi_dispense_properties.flowRateByVolume
732
783
  ),
@@ -744,12 +795,20 @@ def build_multi_dispense_properties(
744
795
 
745
796
 
746
797
  def build_transfer_properties(
747
- by_tip_type_setting: SharedByTipTypeSetting,
798
+ transfer_properties: Union[SharedDataTransferProperties, SharedByTipTypeSetting],
748
799
  ) -> TransferProperties:
800
+ if isinstance(transfer_properties, SharedByTipTypeSetting):
801
+ _transfer_properties = SharedDataTransferProperties(
802
+ aspirate=transfer_properties.aspirate,
803
+ singleDispense=transfer_properties.singleDispense,
804
+ multiDispense=transfer_properties.multiDispense,
805
+ )
806
+ else:
807
+ _transfer_properties = transfer_properties
749
808
  return TransferProperties(
750
- _aspirate=build_aspirate_properties(by_tip_type_setting.aspirate),
751
- _dispense=build_single_dispense_properties(by_tip_type_setting.singleDispense),
809
+ _aspirate=build_aspirate_properties(_transfer_properties.aspirate),
810
+ _dispense=build_single_dispense_properties(_transfer_properties.singleDispense),
752
811
  _multi_dispense=build_multi_dispense_properties(
753
- by_tip_type_setting.multiDispense
812
+ _transfer_properties.multiDispense
754
813
  ),
755
814
  )
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import List, Union, Sequence, Optional
2
+ from typing import List, Union, Sequence, Optional, Tuple
3
3
 
4
4
  from opentrons.types import Location, NozzleMapInterface
5
5
  from opentrons.protocols.api_support import instrument
@@ -13,38 +13,44 @@ from opentrons.protocols.advanced_control.transfers.common import (
13
13
 
14
14
  from .disposal_locations import TrashBin, WasteChute
15
15
  from .labware import Labware, Well
16
+ from .core.common import WellCore
16
17
  from . import validation
17
18
 
18
19
 
19
20
  @dataclass
20
21
  class TransferInfo:
21
22
 
22
- sources_list: List[Well]
23
- destinations_list: List[Well]
23
+ source: List[Well]
24
+ dest: Union[List[Well], TrashBin, WasteChute]
24
25
  tip_policy: TransferTipPolicyV2
25
26
  tip_racks: List[Labware]
26
27
  trash_location: Union[Location, TrashBin, WasteChute]
28
+ last_tip_location: Optional[Tuple[Location, WellCore]]
27
29
 
28
30
 
29
31
  def verify_and_normalize_transfer_args(
30
32
  source: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
31
- dest: Union[Well, Sequence[Well], Sequence[Sequence[Well]]],
33
+ dest: Union[Well, Sequence[Well], Sequence[Sequence[Well]], TrashBin, WasteChute],
32
34
  tip_policy: TransferTipPolicyV2Type,
33
- last_tip_picked_up_from: Optional[Well],
35
+ last_tip_well: Optional[Well],
34
36
  tip_racks: List[Labware],
35
37
  nozzle_map: NozzleMapInterface,
36
- target_all_wells: bool,
38
+ group_wells_for_multi_channel: bool,
37
39
  current_volume: float,
38
40
  trash_location: Union[Location, Well, Labware, TrashBin, WasteChute],
39
41
  ) -> TransferInfo:
40
42
  flat_sources_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(source)
41
- flat_dests_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(dest)
42
- if not target_all_wells and nozzle_map.tip_count > 1:
43
+ if not isinstance(dest, (TrashBin, WasteChute)):
44
+ flat_dests_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(dest)
45
+ else:
46
+ # If trash bin or waste chute, set this to empty to have less isinstance checks after this
47
+ flat_dests_list = []
48
+ if group_wells_for_multi_channel and nozzle_map.tip_count > 1:
43
49
  flat_sources_list = tx_liquid_utils.group_wells_for_multi_channel_transfer(
44
- flat_sources_list, nozzle_map
50
+ flat_sources_list, nozzle_map, "source"
45
51
  )
46
52
  flat_dests_list = tx_liquid_utils.group_wells_for_multi_channel_transfer(
47
- flat_dests_list, nozzle_map
53
+ flat_dests_list, nozzle_map, "destination"
48
54
  )
49
55
  for well in flat_sources_list + flat_dests_list:
50
56
  instrument.validate_takes_liquid(
@@ -55,14 +61,14 @@ def verify_and_normalize_transfer_args(
55
61
 
56
62
  valid_new_tip = validation.ensure_new_tip_policy(tip_policy)
57
63
  if valid_new_tip == TransferTipPolicyV2.NEVER:
58
- if last_tip_picked_up_from is None:
64
+ if last_tip_well is None:
59
65
  raise RuntimeError(
60
66
  "Pipette has no tip attached to perform transfer."
61
67
  " Either do a pick_up_tip beforehand or specify a new_tip parameter"
62
68
  " of 'once' or 'always'."
63
69
  )
64
70
  else:
65
- valid_tip_racks = [last_tip_picked_up_from.parent]
71
+ valid_tip_racks = [last_tip_well.parent]
66
72
  else:
67
73
  valid_tip_racks = tip_racks
68
74
  if current_volume != 0:
@@ -82,10 +88,33 @@ def verify_and_normalize_transfer_args(
82
88
  trash_location=_trash_location
83
89
  )
84
90
 
91
+ if last_tip_well is not None:
92
+ parent_tip_rack = last_tip_well.parent
93
+ last_tip_location = (
94
+ Location(last_tip_well.top().point, parent_tip_rack),
95
+ last_tip_well._core,
96
+ )
97
+ else:
98
+ last_tip_location = None
99
+
85
100
  return TransferInfo(
86
- sources_list=flat_sources_list,
87
- destinations_list=flat_dests_list,
101
+ source=flat_sources_list,
102
+ dest=flat_dests_list if not isinstance(dest, (TrashBin, WasteChute)) else dest,
88
103
  tip_policy=valid_new_tip,
89
104
  tip_racks=valid_tip_racks,
90
105
  trash_location=valid_trash_location,
106
+ last_tip_location=last_tip_location,
91
107
  )
108
+
109
+
110
+ def resolve_keep_last_tip(
111
+ keep_last_tip: Optional[bool], tip_strategy: TransferTipPolicyV2
112
+ ) -> bool:
113
+ """Resolve the liquid class transfer argument `keep_last_tip`
114
+
115
+ If set to a boolean value, maintains that setting. Otherwise, default to
116
+ `True` if tip policy is `NEVER`, otherwise default to `False`
117
+ """
118
+ if keep_last_tip is not None:
119
+ return keep_last_tip
120
+ return tip_strategy == TransferTipPolicyV2.NEVER