opentrons 8.4.1a1__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.1a1.dist-info → opentrons-8.5.0.dist-info}/METADATA +4 -4
  63. {opentrons-8.4.1a1.dist-info → opentrons-8.5.0.dist-info}/RECORD +67 -66
  64. {opentrons-8.4.1a1.dist-info → opentrons-8.5.0.dist-info}/LICENSE +0 -0
  65. {opentrons-8.4.1a1.dist-info → opentrons-8.5.0.dist-info}/WHEEL +0 -0
  66. {opentrons-8.4.1a1.dist-info → opentrons-8.5.0.dist-info}/entry_points.txt +0 -0
  67. {opentrons-8.4.1a1.dist-info → opentrons-8.5.0.dist-info}/top_level.txt +0 -0
@@ -346,6 +346,16 @@ class Well:
346
346
  """Get the current liquid volume in a well."""
347
347
  return self._core.get_liquid_volume()
348
348
 
349
+ @requires_version(2, 24)
350
+ def volume_from_height(self, height: LiquidTrackingType) -> LiquidTrackingType:
351
+ """Return the volume contained in a well at any height."""
352
+ return self._core.volume_from_height(height)
353
+
354
+ @requires_version(2, 24)
355
+ def height_from_volume(self, volume: LiquidTrackingType) -> LiquidTrackingType:
356
+ """Return the height in a well corresponding to a given volume."""
357
+ return self._core.height_from_volume(volume)
358
+
349
359
  @requires_version(2, 21)
350
360
  def estimate_liquid_height_after_pipetting(
351
361
  self,
@@ -177,7 +177,7 @@ class ModuleContext(CommandPublisher):
177
177
 
178
178
  labware_core = self._protocol_core.load_labware(
179
179
  load_name=name,
180
- label=label,
180
+ label=label if label is None else str(label),
181
181
  namespace=namespace,
182
182
  version=version,
183
183
  location=load_location,
@@ -246,7 +246,10 @@ class ModuleContext(CommandPublisher):
246
246
  """
247
247
  _log.warning("load_labware_by_name is deprecated. Use load_labware instead.")
248
248
  return self.load_labware(
249
- name=name, label=label, namespace=namespace, version=version
249
+ name=name,
250
+ label=label,
251
+ namespace=namespace,
252
+ version=version,
250
253
  )
251
254
 
252
255
  @requires_version(2, 15)
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ from copy import deepcopy
4
5
  from typing import (
5
6
  Callable,
6
7
  Dict,
@@ -13,6 +14,11 @@ from typing import (
13
14
  )
14
15
 
15
16
  from opentrons_shared_data.labware.types import LabwareDefinition
17
+ from opentrons_shared_data.liquid_classes.liquid_class_definition import (
18
+ TransferProperties as SharedTransferProperties,
19
+ )
20
+ from opentrons_shared_data.liquid_classes import DEFAULT_LC_VERSION, definition_exists
21
+ from opentrons_shared_data.liquid_classes.types import TransferPropertiesDict
16
22
  from opentrons_shared_data.pipette.types import PipetteNameType
17
23
 
18
24
  from opentrons.types import Mount, Location, DeckLocation, DeckSlotName, StagingSlotName
@@ -48,6 +54,7 @@ from opentrons.protocols.api_support.util import (
48
54
  )
49
55
  from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated
50
56
  from opentrons.protocol_engine.errors import LabwareMovementNotAllowedError
57
+ from ._liquid_properties import build_transfer_properties
51
58
 
52
59
  from ._types import OffDeckType
53
60
  from .core.common import ModuleCore, LabwareCore, ProtocolCore
@@ -196,6 +203,7 @@ class ProtocolContext(CommandPublisher):
196
203
  core=self._core.load_robot(),
197
204
  protocol_core=self._core,
198
205
  api_version=self._api_version,
206
+ broker=broker,
199
207
  )
200
208
  except APIVersionError:
201
209
  self._robot = None
@@ -491,7 +499,7 @@ class ProtocolContext(CommandPublisher):
491
499
  labware_core = self._core.load_labware(
492
500
  load_name=load_name,
493
501
  location=load_location,
494
- label=label,
502
+ label=label if label is None else str(label),
495
503
  namespace=namespace,
496
504
  version=version,
497
505
  )
@@ -1189,9 +1197,16 @@ class ProtocolContext(CommandPublisher):
1189
1197
  self._core.home()
1190
1198
 
1191
1199
  @property
1192
- def location_cache(self) -> Optional[Location]:
1193
- """The cache used by the robot to determine where it last was."""
1194
- return self._core.get_last_location()
1200
+ def location_cache(self) -> Optional[Union[Location, TrashBin, WasteChute]]:
1201
+ """The cache used by the robot to determine where it last was.
1202
+
1203
+ .. versionchanged:: 2.24
1204
+ Can return a ``TrashBin`` or ``WasteChute`` object.
1205
+ """
1206
+ last_loc = self._core.get_last_location()
1207
+ if isinstance(last_loc, Location) or self._api_version >= APIVersion(2, 24):
1208
+ return last_loc
1209
+ return None
1195
1210
 
1196
1211
  @location_cache.setter
1197
1212
  def location_cache(self, loc: Optional[Location]) -> None:
@@ -1356,19 +1371,69 @@ class ProtocolContext(CommandPublisher):
1356
1371
  display_color=display_color,
1357
1372
  )
1358
1373
 
1359
- @requires_version(2, 23)
1360
- def define_liquid_class(
1374
+ @requires_version(2, 24)
1375
+ def get_liquid_class(
1361
1376
  self,
1362
1377
  name: str,
1363
1378
  ) -> LiquidClass:
1364
1379
  """
1365
- Define a liquid class for use in the protocol.
1366
- ..
1367
- This is intended for Opentrons internal use only and is not a guaranteed API.
1380
+ Get an instance of an Opentrons-verified liquid class for use in a Flex protocol.
1368
1381
 
1369
- :meta private:
1382
+ :param name: Name of an Opentrons-verified liquid class. Must be one of:
1383
+
1384
+ - ``"water"``: an Opentrons-verified liquid class based on deionized water.
1385
+ - ``"glycerol_50"``: an Opentrons-verified liquid class for viscous liquid. Based on 50% glycerol.
1386
+ - ``"ethanol_80"``: an Opentrons-verified liquid class for volatile liquid. Based on 80% ethanol.
1387
+
1388
+ :raises: ``LiquidClassDefinitionDoesNotExist``: if the specified liquid class does not exist.
1370
1389
  """
1371
- return self._core.define_liquid_class(name=name)
1390
+ return self._core.get_liquid_class(name=name, version=DEFAULT_LC_VERSION)
1391
+
1392
+ @requires_version(2, 24)
1393
+ def define_liquid_class(
1394
+ self,
1395
+ name: str,
1396
+ properties: Dict[str, Dict[str, TransferPropertiesDict]],
1397
+ base_liquid_class: Optional[LiquidClass] = None,
1398
+ display_name: Optional[str] = None,
1399
+ ) -> LiquidClass:
1400
+ """Define a custom liquid class, either based on an existing, Opentrons-verified liquid class, or to create a completely new one.
1401
+
1402
+ :param name: The name to give to the new liquid class. Cannot use the name of an Opentrons-verified liquid class.
1403
+ :param properties: A dict of transfer properties for the Flex pipette and tips to use for liquid class transfers. The nested dictionary must have top-level keys corresponding to pipette load names and second-level keys corresponding to compatible tip rack load names. Further nested key–value pairs should be in the format returned by :py:meth:`.LiquidClass.get_for`. See also the `liquid class JSON schema <https://github.com/Opentrons/opentrons/tree/edge/shared-data/liquid-class/schemas>`_.
1404
+
1405
+ :param base_liquid_class: An Opentrons-verified liquid class to base the newly defined liquid class on. The specified ``transfer_properties`` will override any existing properties for the Flex pipette and tips. All other properties will remain the same as those in the base class.
1406
+
1407
+ :param display_name: An optional name for the liquid class. Defaults to the title-case ``name`` if a display name isn't provided.
1408
+
1409
+ """
1410
+ if definition_exists(name, DEFAULT_LC_VERSION):
1411
+ raise ValueError(
1412
+ f"Liquid class named {name} already exists. Please specify a different name."
1413
+ )
1414
+ new_liquid_class: LiquidClass
1415
+ if base_liquid_class:
1416
+ # If base liquid is provided, copy to new class
1417
+ # and replace the entries mentioned in transfer props arg
1418
+ new_liquid_class = deepcopy(base_liquid_class)
1419
+ else:
1420
+ new_liquid_class = LiquidClass.create_from(
1421
+ name=name,
1422
+ display_name=display_name or name.title(),
1423
+ by_pipette_setting={},
1424
+ )
1425
+ for pipette, by_tiprack_props in properties.items():
1426
+ for tiprack, transfer_props in by_tiprack_props.items():
1427
+ new_liquid_class.update_for(
1428
+ pipette=pipette,
1429
+ tip_rack=tiprack,
1430
+ transfer_properties=build_transfer_properties(
1431
+ transfer_properties=SharedTransferProperties.model_validate(
1432
+ transfer_props
1433
+ )
1434
+ ),
1435
+ )
1436
+ return new_liquid_class
1372
1437
 
1373
1438
  @property
1374
1439
  @requires_version(2, 5)
@@ -9,6 +9,8 @@ from opentrons.types import (
9
9
  AxisType,
10
10
  StringAxisMap,
11
11
  )
12
+ from opentrons.legacy_broker import LegacyBroker
13
+ from opentrons.legacy_commands import robot_commands as cmds
12
14
  from opentrons.legacy_commands import publisher
13
15
  from opentrons.hardware_control import SyncHardwareAPI
14
16
  from opentrons.protocols.api_support.util import requires_version
@@ -49,8 +51,13 @@ class RobotContext(publisher.CommandPublisher):
49
51
  """
50
52
 
51
53
  def __init__(
52
- self, core: RobotCore, protocol_core: ProtocolCore, api_version: APIVersion
54
+ self,
55
+ core: RobotCore,
56
+ protocol_core: ProtocolCore,
57
+ api_version: APIVersion,
58
+ broker: Optional[LegacyBroker] = None,
53
59
  ) -> None:
60
+ super().__init__(broker)
54
61
  self._hardware = HardwareManager(hardware=protocol_core.get_hardware())
55
62
  self._core = core
56
63
  self._protocol_core = protocol_core
@@ -87,7 +94,16 @@ class RobotContext(publisher.CommandPublisher):
87
94
  :param speed:
88
95
  """
89
96
  mount = validation.ensure_instrument_mount(mount)
90
- self._core.move_to(mount, destination.point, speed)
97
+ with publisher.publish_context(
98
+ broker=self.broker,
99
+ command=cmds.move_to(
100
+ # This needs to be called from protocol context and not the command for import loop reasons
101
+ mount=mount,
102
+ location=destination,
103
+ speed=speed,
104
+ ),
105
+ ):
106
+ self._core.move_to(mount, destination.point, speed)
91
107
 
92
108
  @requires_version(2, 22)
93
109
  def move_axes_to(
@@ -119,7 +135,15 @@ class RobotContext(publisher.CommandPublisher):
119
135
  )
120
136
  else:
121
137
  critical_point = None
122
- self._core.move_axes_to(axis_map, critical_point, speed)
138
+ with publisher.publish_context(
139
+ broker=self.broker,
140
+ command=cmds.move_axis_to(
141
+ # This needs to be called from protocol context and not the command for import loop reasons
142
+ axis_map=axis_map,
143
+ speed=speed,
144
+ ),
145
+ ):
146
+ self._core.move_axes_to(axis_map, critical_point, speed)
123
147
 
124
148
  @requires_version(2, 22)
125
149
  def move_axes_relative(
@@ -142,15 +166,33 @@ class RobotContext(publisher.CommandPublisher):
142
166
  axis_map = validation.ensure_axis_map_type(
143
167
  axis_map, self._protocol_core.robot_type, is_96_channel
144
168
  )
145
- self._core.move_axes_relative(axis_map, speed)
169
+ with publisher.publish_context(
170
+ broker=self.broker,
171
+ command=cmds.move_axis_relative(
172
+ # This needs to be called from protocol context and not the command for import loop reasons
173
+ axis_map=axis_map,
174
+ speed=speed,
175
+ ),
176
+ ):
177
+ self._core.move_axes_relative(axis_map, speed)
146
178
 
147
179
  def close_gripper_jaw(self, force: Optional[float] = None) -> None:
148
180
  """Command the gripper closed with some force."""
149
- self._core.close_gripper(force)
181
+ with publisher.publish_context(
182
+ broker=self.broker,
183
+ command=cmds.close_gripper(
184
+ force=force,
185
+ ),
186
+ ):
187
+ self._core.close_gripper(force)
150
188
 
151
189
  def open_gripper_jaw(self) -> None:
152
190
  """Command the gripper open."""
153
- self._core.release_grip()
191
+ with publisher.publish_context(
192
+ broker=self.broker,
193
+ command=cmds.open_gripper(),
194
+ ):
195
+ self._core.release_grip()
154
196
 
155
197
  def axis_coordinates_for(
156
198
  self,
@@ -545,6 +545,11 @@ class PointTarget(NamedTuple):
545
545
  in_place: bool
546
546
 
547
547
 
548
+ class DisposalTarget(NamedTuple):
549
+ location: Union[TrashBin, WasteChute]
550
+ in_place: bool
551
+
552
+
548
553
  class NoLocationError(ValueError):
549
554
  """Error representing that no location was supplied."""
550
555
 
@@ -553,12 +558,12 @@ class LocationTypeError(TypeError):
553
558
  """Error representing that the location supplied is of different expected type."""
554
559
 
555
560
 
556
- ValidTarget = Union[WellTarget, PointTarget, TrashBin, WasteChute]
561
+ ValidTarget = Union[WellTarget, PointTarget, DisposalTarget]
557
562
 
558
563
 
559
564
  def validate_location(
560
- location: Union[Location, Well, TrashBin, WasteChute, None],
561
- last_location: Optional[Location],
565
+ location: Optional[Union[Location, Well, TrashBin, WasteChute]],
566
+ last_location: Optional[Union[Location, TrashBin, WasteChute]],
562
567
  ) -> ValidTarget:
563
568
  """Validate a given location for a liquid handling command.
564
569
 
@@ -569,9 +574,11 @@ def validate_location(
569
574
  Returns:
570
575
  A `WellTarget` if the input location represents a well.
571
576
  A `PointTarget` if the input location is an x, y, z coordinate.
577
+ A `TrashBin` if the input location is a trash bin
578
+ A `WasteChute` if the input location is a waste chute
572
579
 
573
580
  Raises:
574
- NoLocationError: The is no input location and no cached loaction.
581
+ NoLocationError: There is no input location and no cached location.
575
582
  LocationTypeError: The location supplied is of unexpected type.
576
583
  """
577
584
  from .labware import Well
@@ -586,11 +593,11 @@ def validate_location(
586
593
  f"location should be a Well, Location, TrashBin or WasteChute, but it is {location}"
587
594
  )
588
595
 
589
- if isinstance(target_location, (TrashBin, WasteChute)):
590
- return target_location
591
-
592
596
  in_place = target_location == last_location
593
597
 
598
+ if isinstance(target_location, (TrashBin, WasteChute)):
599
+ return DisposalTarget(location=target_location, in_place=in_place)
600
+
594
601
  if isinstance(target_location, Well):
595
602
  return WellTarget(well=target_location, location=None, in_place=in_place)
596
603
 
@@ -662,7 +669,7 @@ def ensure_new_tip_policy(value: str) -> TransferTipPolicyV2:
662
669
  except ValueError:
663
670
  raise ValueError(
664
671
  f"'{value}' is invalid value for 'new_tip'."
665
- f" Acceptable value is either 'never', 'once', 'always' or 'per source'."
672
+ f" Acceptable value is either 'never', 'once', 'always', 'per source' or 'per destination'."
666
673
  )
667
674
 
668
675
 
@@ -498,8 +498,8 @@ Command = Annotated[
498
498
  robot.MoveTo,
499
499
  robot.MoveAxesRelative,
500
500
  robot.MoveAxesTo,
501
- robot.openGripperJaw,
502
- robot.closeGripperJaw,
501
+ robot.OpenGripperJaw,
502
+ robot.CloseGripperJaw,
503
503
  ],
504
504
  Field(discriminator="commandType"),
505
505
  ]
@@ -599,8 +599,8 @@ CommandParams = Union[
599
599
  robot.MoveAxesRelativeParams,
600
600
  robot.MoveAxesToParams,
601
601
  robot.MoveToParams,
602
- robot.openGripperJawParams,
603
- robot.closeGripperJawParams,
602
+ robot.OpenGripperJawParams,
603
+ robot.CloseGripperJawParams,
604
604
  ]
605
605
 
606
606
  CommandType = Union[
@@ -698,8 +698,8 @@ CommandType = Union[
698
698
  robot.MoveAxesRelativeCommandType,
699
699
  robot.MoveAxesToCommandType,
700
700
  robot.MoveToCommandType,
701
- robot.openGripperJawCommandType,
702
- robot.closeGripperJawCommandType,
701
+ robot.OpenGripperJawCommandType,
702
+ robot.CloseGripperJawCommandType,
703
703
  ]
704
704
 
705
705
  CommandCreate = Annotated[
@@ -798,8 +798,8 @@ CommandCreate = Annotated[
798
798
  robot.MoveAxesRelativeCreate,
799
799
  robot.MoveAxesToCreate,
800
800
  robot.MoveToCreate,
801
- robot.openGripperJawCreate,
802
- robot.closeGripperJawCreate,
801
+ robot.OpenGripperJawCreate,
802
+ robot.CloseGripperJawCreate,
803
803
  ],
804
804
  Field(discriminator="commandType"),
805
805
  ]
@@ -906,8 +906,8 @@ CommandResult = Union[
906
906
  robot.MoveAxesRelativeResult,
907
907
  robot.MoveAxesToResult,
908
908
  robot.MoveToResult,
909
- robot.openGripperJawResult,
910
- robot.closeGripperJawResult,
909
+ robot.OpenGripperJawResult,
910
+ robot.CloseGripperJawResult,
911
911
  ]
912
912
 
913
913
 
@@ -11,7 +11,7 @@ from opentrons_shared_data.load import get_shared_data_root
11
11
 
12
12
  def generate_command_schema(version: str) -> str:
13
13
  """Generate a JSON Schema that all valid create commands can validate against."""
14
- schema_as_dict = CommandCreateAdapter.json_schema(mode="validation")
14
+ schema_as_dict = CommandCreateAdapter.json_schema(mode="validation", by_alias=False)
15
15
  schema_as_dict["$id"] = f"opentronsCommandSchemaV{version}"
16
16
  schema_as_dict["$schema"] = "http://json-schema.org/draft-07/schema#"
17
17
  return json.dumps(schema_as_dict, indent=2, sort_keys=True)
@@ -73,8 +73,8 @@ class GetNextTipImplementation(
73
73
  pipette_id = params.pipetteId
74
74
  starting_tip_name = params.startingTipWell
75
75
 
76
- num_tips = self._state_view.tips.get_pipette_active_channels(pipette_id)
77
- nozzle_map = self._state_view.tips.get_pipette_nozzle_map(pipette_id)
76
+ num_tips = self._state_view.pipettes.get_active_channels(pipette_id)
77
+ nozzle_map = self._state_view.pipettes.get_nozzle_configuration(pipette_id)
78
78
 
79
79
  if (
80
80
  starting_tip_name is not None
@@ -165,25 +165,6 @@ class LoadLabwareImplementation(
165
165
  top_labware_definition=loaded_labware.definition,
166
166
  bottom_labware_id=verified_location.labwareId,
167
167
  )
168
- # Validate load location is valid for lids
169
- if labware_validation.validate_definition_is_lid(
170
- definition=loaded_labware.definition
171
- ):
172
- # This parent is assumed to be compatible, unless the lid enumerates
173
- # all its compatible parents and this parent is missing from the list.
174
- parent_is_incompatible = (
175
- loaded_labware.definition.compatibleParentLabware is not None
176
- and self._state_view.labware.get_load_name(
177
- verified_location.labwareId
178
- )
179
- not in loaded_labware.definition.compatibleParentLabware
180
- )
181
-
182
- if parent_is_incompatible:
183
- raise ValueError(
184
- f"Labware Lid {params.loadName} may not be loaded on parent labware"
185
- f" {self._state_view.labware.get_display_name(verified_location.labwareId)}."
186
- )
187
168
 
188
169
  # Validate labware for the absorbance reader
189
170
  if self._is_loading_to_module(
@@ -121,6 +121,12 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
121
121
  labware_id = params.labwareId
122
122
  well_name = params.wellName
123
123
 
124
+ tips_to_mark_as_used = self._state_view.tips.compute_tips_to_mark_as_used(
125
+ labware_id=labware_id,
126
+ well_name=well_name,
127
+ nozzle_map=self._state_view.pipettes.get_nozzle_configuration(pipette_id),
128
+ )
129
+
124
130
  well_location = self._state_view.geometry.convert_pick_up_tip_well_location(
125
131
  well_location=params.wellLocation
126
132
  )
@@ -152,7 +158,7 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
152
158
  )
153
159
  .set_fluid_empty(pipette_id=pipette_id, clean_tip=True)
154
160
  .mark_tips_as_used(
155
- pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
161
+ labware_id=labware_id, well_names=tips_to_mark_as_used
156
162
  )
157
163
  )
158
164
  state_update = (
@@ -160,7 +166,7 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
160
166
  update_types.StateUpdate(), move_result.state_update
161
167
  )
162
168
  .mark_tips_as_used(
163
- pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
169
+ labware_id=labware_id, well_names=tips_to_mark_as_used
164
170
  )
165
171
  .set_fluid_unknown(pipette_id=pipette_id)
166
172
  )
@@ -186,7 +192,7 @@ class PickUpTipImplementation(AbstractCommandImpl[PickUpTipParams, _ExecuteRetur
186
192
  tip_geometry=tip_geometry,
187
193
  )
188
194
  .mark_tips_as_used(
189
- pipette_id=pipette_id, labware_id=labware_id, well_name=well_name
195
+ labware_id=labware_id, well_names=tips_to_mark_as_used
190
196
  )
191
197
  .set_fluid_empty(pipette_id=pipette_id, clean_tip=True)
192
198
  .set_pipette_ready_to_aspirate(
@@ -22,18 +22,18 @@ from .move_axes_relative import (
22
22
  MoveAxesRelativeCommandType,
23
23
  )
24
24
  from .open_gripper_jaw import (
25
- openGripperJaw,
26
- openGripperJawCreate,
27
- openGripperJawParams,
28
- openGripperJawResult,
29
- openGripperJawCommandType,
25
+ OpenGripperJaw,
26
+ OpenGripperJawCreate,
27
+ OpenGripperJawParams,
28
+ OpenGripperJawResult,
29
+ OpenGripperJawCommandType,
30
30
  )
31
31
  from .close_gripper_jaw import (
32
- closeGripperJaw,
33
- closeGripperJawCreate,
34
- closeGripperJawParams,
35
- closeGripperJawResult,
36
- closeGripperJawCommandType,
32
+ CloseGripperJaw,
33
+ CloseGripperJawCreate,
34
+ CloseGripperJawParams,
35
+ CloseGripperJawResult,
36
+ CloseGripperJawCommandType,
37
37
  )
38
38
 
39
39
  __all__ = [
@@ -56,15 +56,15 @@ __all__ = [
56
56
  "MoveAxesRelativeResult",
57
57
  "MoveAxesRelativeCommandType",
58
58
  # robot/openGripperJaw
59
- "openGripperJaw",
60
- "openGripperJawCreate",
61
- "openGripperJawParams",
62
- "openGripperJawResult",
63
- "openGripperJawCommandType",
59
+ "OpenGripperJaw",
60
+ "OpenGripperJawCreate",
61
+ "OpenGripperJawParams",
62
+ "OpenGripperJawResult",
63
+ "OpenGripperJawCommandType",
64
64
  # robot/closeGripperJaw
65
- "closeGripperJaw",
66
- "closeGripperJawCreate",
67
- "closeGripperJawParams",
68
- "closeGripperJawResult",
69
- "closeGripperJawCommandType",
65
+ "CloseGripperJaw",
66
+ "CloseGripperJawCreate",
67
+ "CloseGripperJawParams",
68
+ "CloseGripperJawResult",
69
+ "CloseGripperJawCommandType",
70
70
  ]
@@ -1,6 +1,7 @@
1
1
  """Command models for opening a gripper jaw."""
2
+
2
3
  from __future__ import annotations
3
- from typing import Literal, Type, Optional, Any
4
+ from typing import Literal, Type, Optional, Any, TYPE_CHECKING
4
5
 
5
6
  from pydantic import BaseModel, Field
6
7
  from pydantic.json_schema import SkipJsonSchema
@@ -16,15 +17,18 @@ from ..command import (
16
17
  )
17
18
  from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence
18
19
 
20
+ if TYPE_CHECKING:
21
+ from ...state.state import StateView
22
+
19
23
 
20
- closeGripperJawCommandType = Literal["robot/closeGripperJaw"]
24
+ CloseGripperJawCommandType = Literal["robot/closeGripperJaw"]
21
25
 
22
26
 
23
27
  def _remove_default(s: dict[str, Any]) -> None:
24
28
  s.pop("default", None)
25
29
 
26
30
 
27
- class closeGripperJawParams(BaseModel):
31
+ class CloseGripperJawParams(BaseModel):
28
32
  """Payload required to close a gripper."""
29
33
 
30
34
  force: float | SkipJsonSchema[None] = Field(
@@ -34,53 +38,59 @@ class closeGripperJawParams(BaseModel):
34
38
  )
35
39
 
36
40
 
37
- class closeGripperJawResult(BaseModel):
38
- """Result data from the execution of a closeGripperJaw command."""
41
+ class CloseGripperJawResult(BaseModel):
42
+ """Result data from the execution of a CloseGripperJaw command."""
39
43
 
40
44
  pass
41
45
 
42
46
 
43
- class closeGripperJawImplementation(
44
- AbstractCommandImpl[closeGripperJawParams, SuccessData[closeGripperJawResult]]
47
+ class CloseGripperJawImplementation(
48
+ AbstractCommandImpl[CloseGripperJawParams, SuccessData[CloseGripperJawResult]]
45
49
  ):
46
- """closeGripperJaw command implementation."""
50
+ """CloseGripperJaw command implementation."""
47
51
 
48
52
  def __init__(
49
53
  self,
50
54
  hardware_api: HardwareControlAPI,
55
+ state_view: StateView,
51
56
  **kwargs: object,
52
57
  ) -> None:
53
58
  self._hardware_api = hardware_api
59
+ self._state_view = state_view
54
60
 
55
61
  async def execute(
56
- self, params: closeGripperJawParams
57
- ) -> SuccessData[closeGripperJawResult]:
62
+ self, params: CloseGripperJawParams
63
+ ) -> SuccessData[CloseGripperJawResult]:
58
64
  """Release the gripper."""
65
+ if self._state_view.config.use_virtual_gripper:
66
+ return SuccessData(
67
+ public=CloseGripperJawResult(),
68
+ )
59
69
  ot3_hardware_api = ensure_ot3_hardware(self._hardware_api)
60
70
  await ot3_hardware_api.grip(force_newtons=params.force)
61
71
  return SuccessData(
62
- public=closeGripperJawResult(),
72
+ public=CloseGripperJawResult(),
63
73
  )
64
74
 
65
75
 
66
- class closeGripperJaw(
67
- BaseCommand[closeGripperJawParams, closeGripperJawResult, ErrorOccurrence]
76
+ class CloseGripperJaw(
77
+ BaseCommand[CloseGripperJawParams, CloseGripperJawResult, ErrorOccurrence]
68
78
  ):
69
- """closeGripperJaw command model."""
79
+ """CloseGripperJaw command model."""
70
80
 
71
- commandType: closeGripperJawCommandType = "robot/closeGripperJaw"
72
- params: closeGripperJawParams
73
- result: Optional[closeGripperJawResult] = None
81
+ commandType: CloseGripperJawCommandType = "robot/closeGripperJaw"
82
+ params: CloseGripperJawParams
83
+ result: Optional[CloseGripperJawResult] = None
74
84
 
75
85
  _ImplementationCls: Type[
76
- closeGripperJawImplementation
77
- ] = closeGripperJawImplementation
86
+ CloseGripperJawImplementation
87
+ ] = CloseGripperJawImplementation
78
88
 
79
89
 
80
- class closeGripperJawCreate(BaseCommandCreate[closeGripperJawParams]):
81
- """closeGripperJaw command request model."""
90
+ class CloseGripperJawCreate(BaseCommandCreate[CloseGripperJawParams]):
91
+ """CloseGripperJaw command request model."""
82
92
 
83
- commandType: closeGripperJawCommandType = "robot/closeGripperJaw"
84
- params: closeGripperJawParams
93
+ commandType: CloseGripperJawCommandType = "robot/closeGripperJaw"
94
+ params: CloseGripperJawParams
85
95
 
86
- _CommandCls: Type[closeGripperJaw] = closeGripperJaw
96
+ _CommandCls: Type[CloseGripperJaw] = CloseGripperJaw