opentrons 8.7.0a2__py3-none-any.whl → 8.7.0a3__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.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/_version.py +2 -2
- opentrons/drivers/thermocycler/abstract.py +0 -1
- opentrons/drivers/thermocycler/driver.py +4 -33
- opentrons/drivers/thermocycler/simulator.py +0 -2
- opentrons/hardware_control/api.py +5 -24
- opentrons/hardware_control/backends/controller.py +2 -8
- opentrons/hardware_control/backends/ot3controller.py +0 -3
- opentrons/hardware_control/backends/ot3simulator.py +1 -2
- opentrons/hardware_control/backends/simulator.py +1 -2
- opentrons/hardware_control/backends/subsystem_manager.py +2 -5
- opentrons/hardware_control/module_control.py +8 -82
- opentrons/hardware_control/modules/__init__.py +0 -3
- opentrons/hardware_control/modules/absorbance_reader.py +4 -11
- opentrons/hardware_control/modules/flex_stacker.py +9 -38
- opentrons/hardware_control/modules/heater_shaker.py +5 -30
- opentrons/hardware_control/modules/magdeck.py +4 -8
- opentrons/hardware_control/modules/mod_abc.py +5 -13
- opentrons/hardware_control/modules/tempdeck.py +5 -25
- opentrons/hardware_control/modules/thermocycler.py +10 -56
- opentrons/hardware_control/modules/types.py +1 -20
- opentrons/hardware_control/modules/utils.py +4 -11
- opentrons/hardware_control/nozzle_manager.py +0 -3
- opentrons/hardware_control/ot3api.py +5 -26
- opentrons/hardware_control/scripts/update_module_fw.py +0 -5
- opentrons/hardware_control/types.py +2 -31
- opentrons/legacy_commands/protocol_commands.py +0 -20
- opentrons/legacy_commands/types.py +0 -42
- opentrons/motion_planning/waypoints.py +29 -15
- opentrons/protocol_api/__init__.py +0 -5
- opentrons/protocol_api/_types.py +1 -6
- opentrons/protocol_api/core/common.py +1 -3
- opentrons/protocol_api/core/engine/_default_labware_versions.py +11 -32
- opentrons/protocol_api/core/engine/labware.py +1 -8
- opentrons/protocol_api/core/engine/module_core.py +0 -4
- opentrons/protocol_api/core/engine/protocol.py +43 -23
- opentrons/protocol_api/core/legacy/legacy_module_core.py +0 -2
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +1 -11
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +2 -14
- opentrons/protocol_api/core/module.py +0 -1
- opentrons/protocol_api/core/protocol.py +2 -11
- opentrons/protocol_api/module_contexts.py +0 -1
- opentrons/protocol_api/protocol_context.py +4 -26
- opentrons/protocol_api/robot_context.py +21 -38
- opentrons/protocol_api/validation.py +1 -6
- opentrons/protocol_engine/actions/__init__.py +2 -4
- opentrons/protocol_engine/actions/actions.py +9 -22
- opentrons/protocol_engine/clients/sync_client.py +7 -6
- opentrons/protocol_engine/commands/__init__.py +0 -42
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +15 -2
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +15 -2
- opentrons/protocol_engine/commands/aspirate.py +0 -1
- opentrons/protocol_engine/commands/command.py +0 -1
- opentrons/protocol_engine/commands/command_unions.py +0 -39
- opentrons/protocol_engine/commands/dispense.py +0 -1
- opentrons/protocol_engine/commands/drop_tip.py +8 -32
- opentrons/protocol_engine/commands/movement_common.py +0 -2
- opentrons/protocol_engine/commands/pick_up_tip.py +11 -21
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +0 -6
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +0 -8
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +1 -17
- opentrons/protocol_engine/commands/touch_tip.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +22 -6
- opentrons/protocol_engine/errors/__init__.py +0 -4
- opentrons/protocol_engine/errors/exceptions.py +0 -55
- opentrons/protocol_engine/execution/__init__.py +0 -2
- opentrons/protocol_engine/execution/command_executor.py +0 -8
- opentrons/protocol_engine/execution/create_queue_worker.py +1 -5
- opentrons/protocol_engine/execution/labware_movement.py +12 -9
- opentrons/protocol_engine/execution/movement.py +0 -2
- opentrons/protocol_engine/execution/queue_worker.py +0 -4
- opentrons/protocol_engine/execution/run_control.py +0 -8
- opentrons/protocol_engine/protocol_engine.py +33 -67
- opentrons/protocol_engine/resources/__init__.py +0 -2
- opentrons/protocol_engine/resources/deck_configuration_provider.py +0 -7
- opentrons/protocol_engine/resources/labware_validation.py +6 -10
- opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
- opentrons/protocol_engine/state/_well_math.py +18 -60
- opentrons/protocol_engine/state/addressable_areas.py +0 -2
- opentrons/protocol_engine/state/commands.py +7 -7
- opentrons/protocol_engine/state/geometry.py +374 -204
- opentrons/protocol_engine/state/labware.py +102 -52
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +0 -37
- opentrons/protocol_engine/state/modules.py +8 -21
- opentrons/protocol_engine/state/motion.py +0 -44
- opentrons/protocol_engine/state/state.py +0 -14
- opentrons/protocol_engine/state/state_summary.py +0 -2
- opentrons/protocol_engine/state/tips.py +258 -177
- opentrons/protocol_engine/state/update_types.py +9 -16
- opentrons/protocol_engine/types/__init__.py +3 -9
- opentrons/protocol_engine/types/deck_configuration.py +1 -5
- opentrons/protocol_engine/types/instrument.py +1 -8
- opentrons/protocol_engine/types/labware.py +13 -1
- opentrons/protocol_engine/types/module.py +0 -10
- opentrons/protocol_engine/types/tip.py +0 -9
- opentrons/protocol_runner/create_simulating_orchestrator.py +2 -29
- opentrons/protocol_runner/run_orchestrator.py +2 -18
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/types.py +1 -2
- opentrons/simulate.py +15 -48
- opentrons/system/camera.py +1 -1
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a3.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a3.dist-info}/RECORD +105 -118
- opentrons/protocol_api/core/engine/tasks.py +0 -35
- opentrons/protocol_api/core/legacy/tasks.py +0 -19
- opentrons/protocol_api/core/legacy_simulator/tasks.py +0 -19
- opentrons/protocol_api/core/tasks.py +0 -31
- opentrons/protocol_api/tasks.py +0 -48
- opentrons/protocol_engine/commands/create_timer.py +0 -83
- opentrons/protocol_engine/commands/set_tip_state.py +0 -97
- opentrons/protocol_engine/commands/wait_for_tasks.py +0 -98
- opentrons/protocol_engine/execution/task_handler.py +0 -157
- opentrons/protocol_engine/resources/concurrency_provider.py +0 -27
- opentrons/protocol_engine/state/labware_origin_math/errors.py +0 -94
- opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +0 -1331
- opentrons/protocol_engine/state/tasks.py +0 -139
- opentrons/protocol_engine/types/tasks.py +0 -38
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a3.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a3.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a2.dist-info → opentrons-8.7.0a3.dist-info}/licenses/LICENSE +0 -0
|
@@ -24,7 +24,6 @@ from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN
|
|
|
24
24
|
from opentrons_shared_data.labware.labware_definition import (
|
|
25
25
|
LabwareDefinition,
|
|
26
26
|
LabwareDefinition2,
|
|
27
|
-
LabwareDefinition3,
|
|
28
27
|
InnerWellGeometry,
|
|
29
28
|
)
|
|
30
29
|
from opentrons_shared_data.deck.types import CutoutFixture
|
|
@@ -45,6 +44,7 @@ from ..errors.exceptions import (
|
|
|
45
44
|
)
|
|
46
45
|
from ..resources import (
|
|
47
46
|
fixture_validation,
|
|
47
|
+
labware_validation,
|
|
48
48
|
deck_configuration_provider,
|
|
49
49
|
)
|
|
50
50
|
from ..types import (
|
|
@@ -63,10 +63,12 @@ from ..types import (
|
|
|
63
63
|
ModuleLocation,
|
|
64
64
|
OnLabwareLocation,
|
|
65
65
|
LabwareLocation,
|
|
66
|
+
LabwareOffsetVector,
|
|
66
67
|
ModuleOffsetData,
|
|
67
68
|
CurrentWell,
|
|
68
69
|
CurrentPipetteLocation,
|
|
69
70
|
TipGeometry,
|
|
71
|
+
LabwareMovementOffsetData,
|
|
70
72
|
InStackerHopperLocation,
|
|
71
73
|
OnDeckLabwareLocation,
|
|
72
74
|
AddressableAreaLocation,
|
|
@@ -89,7 +91,7 @@ from ..types import (
|
|
|
89
91
|
labware_location_is_system,
|
|
90
92
|
WellLocationType,
|
|
91
93
|
WellLocationFunction,
|
|
92
|
-
|
|
94
|
+
LabwareParentDefinition,
|
|
93
95
|
AddressableArea,
|
|
94
96
|
)
|
|
95
97
|
from ..types.liquid_level_detection import SimulatedProbeResult, LiquidTrackingType
|
|
@@ -106,11 +108,8 @@ from .inner_well_math_utils import (
|
|
|
106
108
|
find_volume_user_defined_volumes,
|
|
107
109
|
)
|
|
108
110
|
from ._well_math import wells_covered_by_pipette_configuration, nozzles_per_well
|
|
109
|
-
from .
|
|
110
|
-
|
|
111
|
-
LabwareOriginContext,
|
|
112
|
-
LabwareStackupAncestorDefinition,
|
|
113
|
-
)
|
|
111
|
+
from ._labware_origin_math import get_parent_placement_origin_to_lw_origin
|
|
112
|
+
|
|
114
113
|
|
|
115
114
|
_LOG = getLogger(__name__)
|
|
116
115
|
SLOT_WIDTH = 128
|
|
@@ -126,6 +125,13 @@ class _TipDropSection(enum.Enum):
|
|
|
126
125
|
RIGHT = "right"
|
|
127
126
|
|
|
128
127
|
|
|
128
|
+
class _GripperMoveType(enum.Enum):
|
|
129
|
+
"""Types of gripper movement."""
|
|
130
|
+
|
|
131
|
+
PICK_UP_LABWARE = enum.auto()
|
|
132
|
+
DROP_LABWARE = enum.auto()
|
|
133
|
+
|
|
134
|
+
|
|
129
135
|
@dataclass
|
|
130
136
|
class _AbsoluteRobotExtents:
|
|
131
137
|
front_left: Dict[MountType, Point]
|
|
@@ -404,114 +410,145 @@ class GeometryView:
|
|
|
404
410
|
"""
|
|
405
411
|
location = self._labware.get(labware_id).location
|
|
406
412
|
definition = self._labware.get_definition(labware_id)
|
|
407
|
-
aa_name = self._get_underlying_addressable_area_name(location)
|
|
408
|
-
# TODO(jh, 08-18-25): Labware locations return the underlying slot as the "on location" for the fixed trash,
|
|
409
|
-
# but the underlying slot's name does not exist in addressable area state. Getting the addressable area from data is
|
|
410
|
-
# a workaround. Investigate further.
|
|
411
|
-
addressable_area = self._addressable_areas._get_addressable_area_from_deck_data(
|
|
412
|
-
aa_name, do_compatibility_check=False
|
|
413
|
-
)
|
|
414
|
-
stackup_lw_defs_locs = self._get_stackup_lw_info_top_to_bottom(
|
|
415
|
-
labware_definition=definition, location=location
|
|
416
|
-
)
|
|
417
|
-
underlying_ancestor_def = self._get_stackup_underlying_ancestor_definition(
|
|
418
|
-
location
|
|
419
|
-
)
|
|
420
|
-
module_parent_to_child_offset = self._get_stackup_module_parent_to_child_offset(
|
|
421
|
-
location
|
|
422
|
-
)
|
|
423
413
|
|
|
424
|
-
slot_front_left = self.
|
|
425
|
-
stackup_origin_to_lw_origin =
|
|
426
|
-
|
|
427
|
-
stackup_lw_info_top_to_bottom=stackup_lw_defs_locs,
|
|
428
|
-
underlying_ancestor_definition=underlying_ancestor_def,
|
|
429
|
-
module_parent_to_child_offset=module_parent_to_child_offset,
|
|
430
|
-
deck_definition=self._addressable_areas.deck_definition,
|
|
431
|
-
slot_name=addressable_area.base_slot,
|
|
414
|
+
slot_front_left = self._get_labware_ancestor_position(labware_id)
|
|
415
|
+
stackup_origin_to_lw_origin = self._get_stackup_placement_origin_to_lw_origin(
|
|
416
|
+
location=location, definition=definition, is_topmost_labware=True
|
|
432
417
|
)
|
|
433
418
|
module_cal_offset = self._get_calibrated_module_offset(location)
|
|
434
419
|
|
|
435
420
|
return slot_front_left + stackup_origin_to_lw_origin + module_cal_offset
|
|
436
421
|
|
|
437
|
-
def
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
422
|
+
def _get_labware_ancestor_position(self, labware_id: str) -> Point:
|
|
423
|
+
"""Get the position of the labware's underlying ancestor."""
|
|
424
|
+
slot_name = self._get_underlying_addressable_area_name(
|
|
425
|
+
self._labware.get(labware_id).location
|
|
426
|
+
)
|
|
427
|
+
parent_pos = self._addressable_areas.get_addressable_area_position(slot_name)
|
|
441
428
|
|
|
442
|
-
|
|
443
|
-
The first entry will always be the definition and location of the given labware itself.
|
|
444
|
-
"""
|
|
445
|
-
definitions_locations_top_to_bottom: list[
|
|
446
|
-
tuple[LabwareDefinition, LabwareLocation]
|
|
447
|
-
] = []
|
|
448
|
-
current_location = location
|
|
449
|
-
current_definition = labware_definition
|
|
429
|
+
return parent_pos
|
|
450
430
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
431
|
+
def _get_stackup_placement_origin_to_lw_origin(
|
|
432
|
+
self,
|
|
433
|
+
location: LabwareLocation,
|
|
434
|
+
definition: LabwareDefinition,
|
|
435
|
+
is_topmost_labware: bool,
|
|
436
|
+
) -> Point:
|
|
437
|
+
"""Get the offset vector from the lowest entity in a stackup to the labware."""
|
|
438
|
+
if isinstance(
|
|
439
|
+
location, (AddressableAreaLocation, DeckSlotLocation, ModuleLocation)
|
|
440
|
+
):
|
|
441
|
+
return self._get_parent_placement_origin_to_lw_origin(
|
|
442
|
+
labware_location=location,
|
|
443
|
+
labware_definition=definition,
|
|
444
|
+
is_topmost_labware=is_topmost_labware,
|
|
445
|
+
)
|
|
446
|
+
elif isinstance(location, OnLabwareLocation):
|
|
447
|
+
parent_id = location.labwareId
|
|
448
|
+
parent_location = self._labware.get(parent_id).location
|
|
449
|
+
parent_definition = self._labware.get_definition(parent_id)
|
|
450
|
+
|
|
451
|
+
parent_placement_origin_to_lw_origin = (
|
|
452
|
+
self._get_parent_placement_origin_to_lw_origin(
|
|
453
|
+
labware_location=location,
|
|
454
|
+
labware_definition=definition,
|
|
455
|
+
is_topmost_labware=is_topmost_labware,
|
|
456
|
+
)
|
|
454
457
|
)
|
|
455
458
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
459
|
+
return (
|
|
460
|
+
parent_placement_origin_to_lw_origin
|
|
461
|
+
+ self._get_stackup_placement_origin_to_lw_origin(
|
|
462
|
+
location=parent_location,
|
|
463
|
+
definition=parent_definition,
|
|
464
|
+
is_topmost_labware=False,
|
|
465
|
+
)
|
|
466
|
+
)
|
|
467
|
+
else:
|
|
468
|
+
raise errors.LabwareNotOnDeckError(
|
|
469
|
+
"Cannot access labware since it is not on the deck. "
|
|
470
|
+
"Either it has been loaded off-deck or its been moved off-deck."
|
|
471
|
+
)
|
|
464
472
|
|
|
465
|
-
def
|
|
466
|
-
self,
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
473
|
+
def _get_parent_placement_origin_to_lw_origin(
|
|
474
|
+
self,
|
|
475
|
+
labware_location: LabwareLocation,
|
|
476
|
+
labware_definition: LabwareDefinition,
|
|
477
|
+
is_topmost_labware: bool,
|
|
478
|
+
) -> Point:
|
|
479
|
+
parent_deck_item = self._get_parent_definition(labware_location)
|
|
470
480
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
module_id=current_location.moduleId,
|
|
476
|
-
)
|
|
481
|
+
if isinstance(labware_location, ModuleLocation):
|
|
482
|
+
module_parent_to_child_offset = (
|
|
483
|
+
self._modules.get_nominal_offset_to_child_from_addressable_area(
|
|
484
|
+
module_id=labware_location.moduleId,
|
|
477
485
|
)
|
|
478
|
-
|
|
486
|
+
)
|
|
487
|
+
return get_parent_placement_origin_to_lw_origin(
|
|
488
|
+
child_labware=labware_definition,
|
|
489
|
+
parent_deck_item=parent_deck_item, # type: ignore[arg-type]
|
|
490
|
+
module_parent_to_child_offset=module_parent_to_child_offset,
|
|
491
|
+
deck_definition=self._addressable_areas.deck_definition,
|
|
492
|
+
is_topmost_labware=is_topmost_labware,
|
|
493
|
+
labware_location=labware_location,
|
|
494
|
+
)
|
|
495
|
+
elif isinstance(labware_location, OnLabwareLocation):
|
|
496
|
+
return get_parent_placement_origin_to_lw_origin(
|
|
497
|
+
child_labware=labware_definition,
|
|
498
|
+
parent_deck_item=parent_deck_item, # type: ignore[arg-type]
|
|
499
|
+
module_parent_to_child_offset=None,
|
|
500
|
+
deck_definition=self._addressable_areas.deck_definition,
|
|
501
|
+
is_topmost_labware=is_topmost_labware,
|
|
502
|
+
labware_location=labware_location,
|
|
503
|
+
)
|
|
504
|
+
elif isinstance(labware_location, (DeckSlotLocation, AddressableAreaLocation)):
|
|
505
|
+
return get_parent_placement_origin_to_lw_origin(
|
|
506
|
+
child_labware=labware_definition,
|
|
507
|
+
parent_deck_item=parent_deck_item, # type: ignore[arg-type]
|
|
508
|
+
module_parent_to_child_offset=None,
|
|
509
|
+
deck_definition=self._addressable_areas.deck_definition,
|
|
510
|
+
is_topmost_labware=is_topmost_labware,
|
|
511
|
+
labware_location=labware_location,
|
|
512
|
+
)
|
|
513
|
+
else:
|
|
514
|
+
raise ValueError(f"Invalid labware location: {labware_location}")
|
|
479
515
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
516
|
+
def _get_parent_definition(
|
|
517
|
+
self, location: LabwareLocation
|
|
518
|
+
) -> LabwareParentDefinition:
|
|
519
|
+
"""Get the parent's definition given the labware's location."""
|
|
520
|
+
if isinstance(location, DeckSlotLocation):
|
|
521
|
+
addressable_area_name = location.slotName.id
|
|
522
|
+
return self._addressable_areas.get_slot_definition(addressable_area_name)
|
|
486
523
|
|
|
487
|
-
|
|
524
|
+
elif isinstance(location, AddressableAreaLocation):
|
|
525
|
+
addressable_area_name = location.addressableAreaName
|
|
526
|
+
return self._addressable_areas.get_addressable_area(addressable_area_name)
|
|
488
527
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
f"Cannot get ancestor slot of location {current_location}"
|
|
514
|
-
)
|
|
528
|
+
elif isinstance(location, ModuleLocation):
|
|
529
|
+
module_id = location.moduleId
|
|
530
|
+
return self._modules.get_definition(module_id)
|
|
531
|
+
|
|
532
|
+
elif isinstance(location, OnLabwareLocation):
|
|
533
|
+
below_labware_id = location.labwareId
|
|
534
|
+
return self._labware.get_definition(below_labware_id)
|
|
535
|
+
|
|
536
|
+
elif location == OFF_DECK_LOCATION or location == SYSTEM_LOCATION:
|
|
537
|
+
raise errors.LabwareNotOnDeckError(
|
|
538
|
+
f"Labware location {location} does not have a slot associated with it"
|
|
539
|
+
f" since it is no longer on the deck."
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
elif isinstance(location, InStackerHopperLocation):
|
|
543
|
+
raise errors.LabwareNotOnDeckError(
|
|
544
|
+
"Labware does not have a slot or module associated with it"
|
|
545
|
+
" since it is no longer on the deck."
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
else:
|
|
549
|
+
raise errors.InvalidLabwarePositionError(
|
|
550
|
+
f"Cannot get ancestor from location {location}"
|
|
551
|
+
)
|
|
515
552
|
|
|
516
553
|
def _get_underlying_addressable_area_name(self, location: LabwareLocation) -> str:
|
|
517
554
|
if isinstance(location, DeckSlotLocation):
|
|
@@ -739,7 +776,7 @@ class GeometryView:
|
|
|
739
776
|
|
|
740
777
|
if well_def.shape != "circular":
|
|
741
778
|
raise errors.LabwareIsNotTipRackError(
|
|
742
|
-
f"Well {well_name} in labware {
|
|
779
|
+
f"Well {well_name} in labware {labware_id} is not circular."
|
|
743
780
|
)
|
|
744
781
|
|
|
745
782
|
return TipGeometry(
|
|
@@ -830,7 +867,7 @@ class GeometryView:
|
|
|
830
867
|
slot_name = DeckSlotName.from_primitive(area_name)
|
|
831
868
|
elif labware.location == OFF_DECK_LOCATION:
|
|
832
869
|
raise errors.LabwareNotOnDeckError(
|
|
833
|
-
f"Labware {
|
|
870
|
+
f"Labware {labware_id} does not have a slot associated with it"
|
|
834
871
|
f" since it is no longer on the deck."
|
|
835
872
|
)
|
|
836
873
|
else:
|
|
@@ -1021,8 +1058,6 @@ class GeometryView:
|
|
|
1021
1058
|
location: Union[
|
|
1022
1059
|
DeckSlotLocation, ModuleLocation, OnLabwareLocation, AddressableAreaLocation
|
|
1023
1060
|
],
|
|
1024
|
-
move_type: GripperMoveType,
|
|
1025
|
-
user_additional_offset: Point | None,
|
|
1026
1061
|
) -> Point:
|
|
1027
1062
|
"""Get the grip point of the labware as placed on the given location.
|
|
1028
1063
|
|
|
@@ -1034,107 +1069,28 @@ class GeometryView:
|
|
|
1034
1069
|
It is calculated as the xy center of the slot with z as the point indicated by
|
|
1035
1070
|
z-position of labware bottom + grip height from labware bottom.
|
|
1036
1071
|
"""
|
|
1037
|
-
mod_cal_offset = self._get_calibrated_module_offset(location)
|
|
1038
|
-
user_additional_offset = user_additional_offset or Point()
|
|
1039
|
-
aa_origin_to_nominal_grip_point = self._get_aa_origin_to_nominal_grip_point(
|
|
1040
|
-
labware_definition=labware_definition,
|
|
1041
|
-
location=location,
|
|
1042
|
-
move_type=move_type,
|
|
1043
|
-
)
|
|
1044
|
-
|
|
1045
|
-
return aa_origin_to_nominal_grip_point + mod_cal_offset + user_additional_offset
|
|
1046
|
-
|
|
1047
|
-
def _get_aa_origin_to_nominal_grip_point(
|
|
1048
|
-
self,
|
|
1049
|
-
labware_definition: LabwareDefinition,
|
|
1050
|
-
location: Union[
|
|
1051
|
-
DeckSlotLocation, ModuleLocation, OnLabwareLocation, AddressableAreaLocation
|
|
1052
|
-
],
|
|
1053
|
-
move_type: GripperMoveType,
|
|
1054
|
-
) -> Point:
|
|
1055
|
-
"""Get the nominal grip point of a labware.
|
|
1056
|
-
|
|
1057
|
-
Does not include module calibration offsets or user additional offsets.
|
|
1058
|
-
"""
|
|
1059
1072
|
grip_z_from_lw_origin = self._labware.get_grip_z(labware_definition)
|
|
1060
1073
|
aa_name = self._get_underlying_addressable_area_name(location)
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
module_parent_to_child_offset = self._get_stackup_module_parent_to_child_offset(
|
|
1066
|
-
location
|
|
1067
|
-
)
|
|
1068
|
-
underlying_ancestor_def = self._get_stackup_underlying_ancestor_definition(
|
|
1069
|
-
location
|
|
1074
|
+
parent_to_lw_offset = self._get_stackup_placement_origin_to_lw_origin(
|
|
1075
|
+
location=location,
|
|
1076
|
+
definition=labware_definition,
|
|
1077
|
+
is_topmost_labware=True, # We aren't concerned with entities above the gripped labware.
|
|
1070
1078
|
)
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
else LabwareOriginContext.GRIPPER_DROPPING
|
|
1079
|
+
addressable_area = self._addressable_areas.get_addressable_area(aa_name)
|
|
1080
|
+
lw_origin_to_parent = self._get_lw_origin_to_parent(
|
|
1081
|
+
labware_definition=labware_definition, addressable_area=addressable_area
|
|
1075
1082
|
)
|
|
1083
|
+
mod_cal_offset = self._get_calibrated_module_offset(location)
|
|
1084
|
+
location_center = self._addressable_areas.get_addressable_area_center(aa_name)
|
|
1076
1085
|
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
deck_definition=self._addressable_areas.deck_definition,
|
|
1086
|
+
return (
|
|
1087
|
+
location_center
|
|
1088
|
+
+ parent_to_lw_offset
|
|
1089
|
+
+ lw_origin_to_parent
|
|
1090
|
+
+ mod_cal_offset
|
|
1091
|
+
+ Point(0, 0, grip_z_from_lw_origin)
|
|
1084
1092
|
)
|
|
1085
1093
|
|
|
1086
|
-
if isinstance(labware_definition, LabwareDefinition2):
|
|
1087
|
-
lw_origin_to_aa_origin = self._get_lw_origin_to_parent(
|
|
1088
|
-
labware_definition=labware_definition, addressable_area=addressable_area
|
|
1089
|
-
)
|
|
1090
|
-
aa_origin_to_aa_center = (
|
|
1091
|
-
self._addressable_areas.get_addressable_area_center(aa_name)
|
|
1092
|
-
)
|
|
1093
|
-
aa_center_to_nominal_grip_point = Point(0, 0, grip_z_from_lw_origin)
|
|
1094
|
-
|
|
1095
|
-
return (
|
|
1096
|
-
aa_origin_to_lw_origin
|
|
1097
|
-
+ lw_origin_to_aa_origin
|
|
1098
|
-
+ aa_origin_to_aa_center
|
|
1099
|
-
+ aa_center_to_nominal_grip_point
|
|
1100
|
-
)
|
|
1101
|
-
|
|
1102
|
-
else:
|
|
1103
|
-
assert isinstance(labware_definition, LabwareDefinition3)
|
|
1104
|
-
|
|
1105
|
-
aa_origin = self._addressable_areas.get_addressable_area_position(aa_name)
|
|
1106
|
-
lw_origin_to_lw_center = self._get_lw_origin_to_lw_center(
|
|
1107
|
-
labware_definition
|
|
1108
|
-
)
|
|
1109
|
-
lw_origin_to_lw_grip_center = Point(
|
|
1110
|
-
x=lw_origin_to_lw_center.x,
|
|
1111
|
-
y=lw_origin_to_lw_center.y,
|
|
1112
|
-
z=grip_z_from_lw_origin,
|
|
1113
|
-
)
|
|
1114
|
-
|
|
1115
|
-
return aa_origin + aa_origin_to_lw_origin + lw_origin_to_lw_grip_center
|
|
1116
|
-
|
|
1117
|
-
def _get_lw_origin_to_lw_center(
|
|
1118
|
-
self, labware_definition: LabwareDefinition
|
|
1119
|
-
) -> Point:
|
|
1120
|
-
"""Get the x,y,z center of the labware."""
|
|
1121
|
-
if isinstance(labware_definition, LabwareDefinition2):
|
|
1122
|
-
dimensions = labware_definition.dimensions
|
|
1123
|
-
x = dimensions.xDimension / 2
|
|
1124
|
-
y = dimensions.yDimension / 2
|
|
1125
|
-
z = dimensions.zDimension / 2
|
|
1126
|
-
|
|
1127
|
-
return Point(x, y, z)
|
|
1128
|
-
else:
|
|
1129
|
-
front_right_top = labware_definition.extents.total.frontRightTop
|
|
1130
|
-
back_left_bottom = labware_definition.extents.total.backLeftBottom
|
|
1131
|
-
|
|
1132
|
-
x = (front_right_top.x - back_left_bottom.x) / 2
|
|
1133
|
-
y = (front_right_top.y - back_left_bottom.y) / 2
|
|
1134
|
-
z = (front_right_top.z - back_left_bottom.z) / 2
|
|
1135
|
-
|
|
1136
|
-
return Point(x, y, z)
|
|
1137
|
-
|
|
1138
1094
|
def _get_lw_origin_to_parent(
|
|
1139
1095
|
self, labware_definition: LabwareDefinition, addressable_area: AddressableArea
|
|
1140
1096
|
) -> Point:
|
|
@@ -1168,6 +1124,7 @@ class GeometryView:
|
|
|
1168
1124
|
if self._modules.should_dodge_thermocycler(
|
|
1169
1125
|
from_slot=from_slot, to_slot=to_slot
|
|
1170
1126
|
):
|
|
1127
|
+
|
|
1171
1128
|
middle_slot_fixture = (
|
|
1172
1129
|
self._addressable_areas.get_fixture_by_deck_slot_name(
|
|
1173
1130
|
DeckSlotName.SLOT_C2.to_equivalent_for_robot_type(
|
|
@@ -1436,6 +1393,41 @@ class GeometryView:
|
|
|
1436
1393
|
x_well_offset = 0
|
|
1437
1394
|
return x_well_offset
|
|
1438
1395
|
|
|
1396
|
+
def get_final_labware_movement_offset_vectors(
|
|
1397
|
+
self,
|
|
1398
|
+
from_location: OnDeckLabwareLocation,
|
|
1399
|
+
to_location: OnDeckLabwareLocation,
|
|
1400
|
+
additional_pick_up_offset: Point,
|
|
1401
|
+
additional_drop_offset: Point,
|
|
1402
|
+
current_labware: LabwareDefinition,
|
|
1403
|
+
) -> LabwareMovementOffsetData:
|
|
1404
|
+
"""Calculate the final labware offset vector to use in labware movement."""
|
|
1405
|
+
pick_up_offset = (
|
|
1406
|
+
self.get_total_nominal_gripper_offset_for_move_type(
|
|
1407
|
+
location=from_location,
|
|
1408
|
+
move_type=_GripperMoveType.PICK_UP_LABWARE,
|
|
1409
|
+
current_labware=current_labware,
|
|
1410
|
+
)
|
|
1411
|
+
+ additional_pick_up_offset
|
|
1412
|
+
)
|
|
1413
|
+
drop_offset = (
|
|
1414
|
+
self.get_total_nominal_gripper_offset_for_move_type(
|
|
1415
|
+
location=to_location,
|
|
1416
|
+
move_type=_GripperMoveType.DROP_LABWARE,
|
|
1417
|
+
current_labware=current_labware,
|
|
1418
|
+
)
|
|
1419
|
+
+ additional_drop_offset
|
|
1420
|
+
)
|
|
1421
|
+
|
|
1422
|
+
return LabwareMovementOffsetData(
|
|
1423
|
+
pickUpOffset=LabwareOffsetVector(
|
|
1424
|
+
x=pick_up_offset.x, y=pick_up_offset.y, z=pick_up_offset.z
|
|
1425
|
+
),
|
|
1426
|
+
dropOffset=LabwareOffsetVector(
|
|
1427
|
+
x=drop_offset.x, y=drop_offset.y, z=drop_offset.z
|
|
1428
|
+
),
|
|
1429
|
+
)
|
|
1430
|
+
|
|
1439
1431
|
@staticmethod
|
|
1440
1432
|
def ensure_valid_gripper_location(
|
|
1441
1433
|
location: LabwareLocation,
|
|
@@ -1457,6 +1449,130 @@ class GeometryView:
|
|
|
1457
1449
|
)
|
|
1458
1450
|
return location
|
|
1459
1451
|
|
|
1452
|
+
def get_total_nominal_gripper_offset_for_move_type(
|
|
1453
|
+
self,
|
|
1454
|
+
location: OnDeckLabwareLocation,
|
|
1455
|
+
move_type: _GripperMoveType,
|
|
1456
|
+
current_labware: LabwareDefinition,
|
|
1457
|
+
) -> Point:
|
|
1458
|
+
"""Get the total of the offsets to be used to pick up labware in its current location."""
|
|
1459
|
+
if move_type == _GripperMoveType.PICK_UP_LABWARE:
|
|
1460
|
+
if isinstance(
|
|
1461
|
+
location, (ModuleLocation, DeckSlotLocation, AddressableAreaLocation)
|
|
1462
|
+
):
|
|
1463
|
+
return Point.from_xyz_attrs(
|
|
1464
|
+
self._nominal_gripper_offsets_for_location(location).pickUpOffset
|
|
1465
|
+
)
|
|
1466
|
+
else:
|
|
1467
|
+
# If it's a labware on a labware (most likely an adapter),
|
|
1468
|
+
# we calculate the offset as sum of offsets for the direct parent labware
|
|
1469
|
+
# and the underlying non-labware parent location.
|
|
1470
|
+
direct_parent_offset = self._nominal_gripper_offsets_for_location(
|
|
1471
|
+
location
|
|
1472
|
+
)
|
|
1473
|
+
ancestor = self._labware.get_parent_location(location.labwareId)
|
|
1474
|
+
extra_offset = Point(x=0, y=0, z=0)
|
|
1475
|
+
if (
|
|
1476
|
+
isinstance(ancestor, ModuleLocation)
|
|
1477
|
+
# todo(mm, 2025-06-20): Avoid this private attribute access.
|
|
1478
|
+
and self._modules._state.requested_model_by_id[ancestor.moduleId]
|
|
1479
|
+
== ModuleModel.THERMOCYCLER_MODULE_V2
|
|
1480
|
+
and labware_validation.validate_definition_is_lid(current_labware)
|
|
1481
|
+
):
|
|
1482
|
+
if "lidOffsets" in current_labware.gripperOffsets.keys():
|
|
1483
|
+
extra_offset = Point(
|
|
1484
|
+
x=current_labware.gripperOffsets[
|
|
1485
|
+
"lidOffsets"
|
|
1486
|
+
].pickUpOffset.x,
|
|
1487
|
+
y=current_labware.gripperOffsets[
|
|
1488
|
+
"lidOffsets"
|
|
1489
|
+
].pickUpOffset.y,
|
|
1490
|
+
z=current_labware.gripperOffsets[
|
|
1491
|
+
"lidOffsets"
|
|
1492
|
+
].pickUpOffset.z,
|
|
1493
|
+
)
|
|
1494
|
+
else:
|
|
1495
|
+
raise errors.LabwareOffsetDoesNotExistError(
|
|
1496
|
+
f"Labware Definition {current_labware.parameters.loadName} does not contain required field 'lidOffsets' of 'gripperOffsets'."
|
|
1497
|
+
)
|
|
1498
|
+
|
|
1499
|
+
assert isinstance(
|
|
1500
|
+
ancestor,
|
|
1501
|
+
(
|
|
1502
|
+
DeckSlotLocation,
|
|
1503
|
+
ModuleLocation,
|
|
1504
|
+
OnLabwareLocation,
|
|
1505
|
+
AddressableAreaLocation,
|
|
1506
|
+
),
|
|
1507
|
+
), "No gripper offsets for off-deck labware"
|
|
1508
|
+
return (
|
|
1509
|
+
Point.from_xyz_attrs(direct_parent_offset.pickUpOffset)
|
|
1510
|
+
+ Point.from_xyz_attrs(
|
|
1511
|
+
self._nominal_gripper_offsets_for_location(
|
|
1512
|
+
location=ancestor
|
|
1513
|
+
).pickUpOffset
|
|
1514
|
+
)
|
|
1515
|
+
+ extra_offset
|
|
1516
|
+
)
|
|
1517
|
+
else:
|
|
1518
|
+
if isinstance(
|
|
1519
|
+
location, (ModuleLocation, DeckSlotLocation, AddressableAreaLocation)
|
|
1520
|
+
):
|
|
1521
|
+
return Point.from_xyz_attrs(
|
|
1522
|
+
self._nominal_gripper_offsets_for_location(location).dropOffset
|
|
1523
|
+
)
|
|
1524
|
+
else:
|
|
1525
|
+
# If it's a labware on a labware (most likely an adapter),
|
|
1526
|
+
# we calculate the offset as sum of offsets for the direct parent labware
|
|
1527
|
+
# and the underlying non-labware parent location.
|
|
1528
|
+
direct_parent_offset = self._nominal_gripper_offsets_for_location(
|
|
1529
|
+
location
|
|
1530
|
+
)
|
|
1531
|
+
ancestor = self._labware.get_parent_location(location.labwareId)
|
|
1532
|
+
extra_offset = Point(x=0, y=0, z=0)
|
|
1533
|
+
if (
|
|
1534
|
+
isinstance(ancestor, ModuleLocation)
|
|
1535
|
+
# todo(mm, 2024-11-06): Do not access private module state; only use public ModuleView methods.
|
|
1536
|
+
and self._modules._state.requested_model_by_id[ancestor.moduleId]
|
|
1537
|
+
== ModuleModel.THERMOCYCLER_MODULE_V2
|
|
1538
|
+
and labware_validation.validate_definition_is_lid(current_labware)
|
|
1539
|
+
):
|
|
1540
|
+
if "lidOffsets" in current_labware.gripperOffsets.keys():
|
|
1541
|
+
extra_offset = Point(
|
|
1542
|
+
x=current_labware.gripperOffsets[
|
|
1543
|
+
"lidOffsets"
|
|
1544
|
+
].pickUpOffset.x,
|
|
1545
|
+
y=current_labware.gripperOffsets[
|
|
1546
|
+
"lidOffsets"
|
|
1547
|
+
].pickUpOffset.y,
|
|
1548
|
+
z=current_labware.gripperOffsets[
|
|
1549
|
+
"lidOffsets"
|
|
1550
|
+
].pickUpOffset.z,
|
|
1551
|
+
)
|
|
1552
|
+
else:
|
|
1553
|
+
raise errors.LabwareOffsetDoesNotExistError(
|
|
1554
|
+
f"Labware Definition {current_labware.parameters.loadName} does not contain required field 'lidOffsets' of 'gripperOffsets'."
|
|
1555
|
+
)
|
|
1556
|
+
|
|
1557
|
+
assert isinstance(
|
|
1558
|
+
ancestor,
|
|
1559
|
+
(
|
|
1560
|
+
DeckSlotLocation,
|
|
1561
|
+
ModuleLocation,
|
|
1562
|
+
OnLabwareLocation,
|
|
1563
|
+
AddressableAreaLocation,
|
|
1564
|
+
),
|
|
1565
|
+
), "No gripper offsets for off-deck labware"
|
|
1566
|
+
return (
|
|
1567
|
+
Point.from_xyz_attrs(direct_parent_offset.dropOffset)
|
|
1568
|
+
+ Point.from_xyz_attrs(
|
|
1569
|
+
self._nominal_gripper_offsets_for_location(
|
|
1570
|
+
location=ancestor
|
|
1571
|
+
).dropOffset
|
|
1572
|
+
)
|
|
1573
|
+
+ extra_offset
|
|
1574
|
+
)
|
|
1575
|
+
|
|
1460
1576
|
# todo(mm, 2024-11-05): This may be incorrect because it does not take the following
|
|
1461
1577
|
# offsets into account, which *are* taken into account for the actual gripper movement:
|
|
1462
1578
|
#
|
|
@@ -1508,6 +1624,64 @@ class GeometryView:
|
|
|
1508
1624
|
f"Cannot move labware '{labware_definition.parameters.loadName}' when {int(tip.volume)} µL tips are attached."
|
|
1509
1625
|
)
|
|
1510
1626
|
|
|
1627
|
+
def _nominal_gripper_offsets_for_location(
|
|
1628
|
+
self, location: OnDeckLabwareLocation
|
|
1629
|
+
) -> LabwareMovementOffsetData:
|
|
1630
|
+
"""Provide the default gripper offset data for the given location type."""
|
|
1631
|
+
if isinstance(location, (DeckSlotLocation, AddressableAreaLocation)):
|
|
1632
|
+
# TODO we might need a separate type of gripper offset for addressable areas but that also might just
|
|
1633
|
+
# be covered by the drop labware offset/location
|
|
1634
|
+
offsets = self._labware.get_deck_default_gripper_offsets()
|
|
1635
|
+
elif isinstance(location, ModuleLocation):
|
|
1636
|
+
offsets = self._modules.get_default_gripper_offsets(location.moduleId)
|
|
1637
|
+
else:
|
|
1638
|
+
# Labware is on a labware/adapter
|
|
1639
|
+
offsets = self._labware_gripper_offsets(location.labwareId)
|
|
1640
|
+
return offsets or LabwareMovementOffsetData(
|
|
1641
|
+
pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0),
|
|
1642
|
+
dropOffset=LabwareOffsetVector(x=0, y=0, z=0),
|
|
1643
|
+
)
|
|
1644
|
+
|
|
1645
|
+
def _labware_gripper_offsets(
|
|
1646
|
+
self, labware_id: str
|
|
1647
|
+
) -> Optional[LabwareMovementOffsetData]:
|
|
1648
|
+
"""Provide the most appropriate gripper offset data for the specified labware.
|
|
1649
|
+
|
|
1650
|
+
We check the types of gripper offsets available for the labware ("default" or slot-based)
|
|
1651
|
+
and return the most appropriate one for the overall location of the labware.
|
|
1652
|
+
Currently, only module adapters (specifically, the H/S universal flat adapter)
|
|
1653
|
+
have non-default offsets that are specific to location of the module on deck,
|
|
1654
|
+
so, this code only checks for the presence of those known offsets.
|
|
1655
|
+
"""
|
|
1656
|
+
parent_location = self._labware.get_parent_location(labware_id)
|
|
1657
|
+
assert isinstance(
|
|
1658
|
+
parent_location,
|
|
1659
|
+
(
|
|
1660
|
+
DeckSlotLocation,
|
|
1661
|
+
ModuleLocation,
|
|
1662
|
+
AddressableAreaLocation,
|
|
1663
|
+
OnLabwareLocation,
|
|
1664
|
+
),
|
|
1665
|
+
), "No gripper offsets for off-deck labware"
|
|
1666
|
+
|
|
1667
|
+
if isinstance(parent_location, DeckSlotLocation):
|
|
1668
|
+
slot_name = parent_location.slotName
|
|
1669
|
+
elif isinstance(parent_location, AddressableAreaLocation):
|
|
1670
|
+
slot_name = self._addressable_areas.get_addressable_area_base_slot(
|
|
1671
|
+
parent_location.addressableAreaName
|
|
1672
|
+
)
|
|
1673
|
+
else:
|
|
1674
|
+
module_loc = self._modules.get_location(parent_location.moduleId)
|
|
1675
|
+
slot_name = module_loc.slotName
|
|
1676
|
+
|
|
1677
|
+
slot_based_offset = self._labware.get_child_gripper_offsets(
|
|
1678
|
+
labware_id=labware_id, slot_name=slot_name.to_ot3_equivalent()
|
|
1679
|
+
)
|
|
1680
|
+
|
|
1681
|
+
return slot_based_offset or self._labware.get_child_gripper_offsets(
|
|
1682
|
+
labware_id=labware_id, slot_name=None
|
|
1683
|
+
)
|
|
1684
|
+
|
|
1511
1685
|
def get_location_sequence(self, labware_id: str) -> LabwareLocationSequence:
|
|
1512
1686
|
"""Provide the LocationSequence specifying the current position of the labware.
|
|
1513
1687
|
|
|
@@ -2114,8 +2288,7 @@ class GeometryView:
|
|
|
2114
2288
|
except InvalidLiquidHeightFound as _exception:
|
|
2115
2289
|
raise InvalidLiquidHeightFound(
|
|
2116
2290
|
message=_exception.message
|
|
2117
|
-
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)}"
|
|
2118
|
-
f" on slot {self.get_ancestor_slot_name(labware_id)}"
|
|
2291
|
+
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)} on slot {self.get_ancestor_slot_name(labware_id)}"
|
|
2119
2292
|
)
|
|
2120
2293
|
# if meniscus volume is a simulated value, comparisons aren't meaningful
|
|
2121
2294
|
if isinstance(meniscus_volume, SimulatedProbeResult):
|
|
@@ -2123,16 +2296,13 @@ class GeometryView:
|
|
|
2123
2296
|
remaining_volume = well_volumetric_capacity - meniscus_volume
|
|
2124
2297
|
if volume > remaining_volume:
|
|
2125
2298
|
raise errors.InvalidDispenseVolumeError(
|
|
2126
|
-
f"Attempting to dispense {volume}µL of liquid into a well that can currently only hold"
|
|
2127
|
-
f" {remaining_volume}µL (well {well_name} in labware {self._labware.get_display_name(labware_id)})"
|
|
2299
|
+
f"Attempting to dispense {volume}µL of liquid into a well that can currently only hold {remaining_volume}µL (well {well_name} in labware_id: {labware_id})"
|
|
2128
2300
|
)
|
|
2129
2301
|
else:
|
|
2130
2302
|
# TODO(pbm, 10-08-24): factor in well (LabwareStore) state volume
|
|
2131
2303
|
if volume > well_volumetric_capacity:
|
|
2132
2304
|
raise errors.InvalidDispenseVolumeError(
|
|
2133
|
-
f"Attempting to dispense {volume}µL of liquid into a well that can only hold"
|
|
2134
|
-
f" {well_volumetric_capacity}µL (well {well_name} in"
|
|
2135
|
-
f" labware {self._labware.get_display_name(labware_id)})"
|
|
2305
|
+
f"Attempting to dispense {volume}µL of liquid into a well that can only hold {well_volumetric_capacity}µL (well {well_name} in labware_id: {labware_id})"
|
|
2136
2306
|
)
|
|
2137
2307
|
|
|
2138
2308
|
def get_wells_covered_by_pipette_with_active_well(
|