opentrons 8.7.0a5__py3-none-any.whl → 8.7.0a7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opentrons might be problematic. Click here for more details.
- opentrons/_version.py +2 -2
- opentrons/drivers/asyncio/communication/serial_connection.py +129 -52
- opentrons/drivers/heater_shaker/abstract.py +5 -0
- opentrons/drivers/heater_shaker/driver.py +10 -0
- opentrons/drivers/heater_shaker/simulator.py +4 -0
- opentrons/drivers/thermocycler/abstract.py +6 -0
- opentrons/drivers/thermocycler/driver.py +61 -10
- opentrons/drivers/thermocycler/simulator.py +6 -0
- opentrons/hardware_control/api.py +24 -5
- opentrons/hardware_control/backends/controller.py +8 -2
- opentrons/hardware_control/backends/ot3controller.py +3 -0
- opentrons/hardware_control/backends/ot3simulator.py +2 -1
- opentrons/hardware_control/backends/simulator.py +2 -1
- opentrons/hardware_control/backends/subsystem_manager.py +5 -2
- opentrons/hardware_control/emulation/abstract_emulator.py +6 -4
- opentrons/hardware_control/emulation/connection_handler.py +8 -5
- opentrons/hardware_control/emulation/heater_shaker.py +12 -3
- opentrons/hardware_control/emulation/settings.py +1 -1
- opentrons/hardware_control/emulation/thermocycler.py +67 -15
- opentrons/hardware_control/module_control.py +82 -8
- opentrons/hardware_control/modules/__init__.py +3 -0
- opentrons/hardware_control/modules/absorbance_reader.py +11 -4
- opentrons/hardware_control/modules/flex_stacker.py +38 -9
- opentrons/hardware_control/modules/heater_shaker.py +42 -5
- opentrons/hardware_control/modules/magdeck.py +8 -4
- opentrons/hardware_control/modules/mod_abc.py +13 -5
- opentrons/hardware_control/modules/tempdeck.py +25 -5
- opentrons/hardware_control/modules/thermocycler.py +68 -11
- opentrons/hardware_control/modules/types.py +20 -1
- opentrons/hardware_control/modules/utils.py +11 -4
- opentrons/hardware_control/nozzle_manager.py +3 -0
- opentrons/hardware_control/ot3api.py +26 -5
- opentrons/hardware_control/poller.py +22 -8
- opentrons/hardware_control/scripts/update_module_fw.py +5 -0
- opentrons/hardware_control/types.py +31 -2
- opentrons/legacy_commands/module_commands.py +23 -0
- opentrons/legacy_commands/protocol_commands.py +20 -0
- opentrons/legacy_commands/types.py +80 -0
- opentrons/motion_planning/deck_conflict.py +17 -12
- opentrons/motion_planning/waypoints.py +15 -29
- opentrons/protocol_api/__init__.py +5 -1
- opentrons/protocol_api/_types.py +6 -1
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/_default_labware_versions.py +32 -11
- opentrons/protocol_api/core/engine/labware.py +8 -1
- opentrons/protocol_api/core/engine/module_core.py +75 -8
- opentrons/protocol_api/core/engine/protocol.py +18 -1
- opentrons/protocol_api/core/engine/tasks.py +48 -0
- opentrons/protocol_api/core/engine/well.py +8 -0
- opentrons/protocol_api/core/legacy/legacy_module_core.py +24 -4
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +11 -1
- opentrons/protocol_api/core/legacy/legacy_well_core.py +4 -0
- opentrons/protocol_api/core/legacy/tasks.py +19 -0
- opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +14 -2
- opentrons/protocol_api/core/legacy_simulator/tasks.py +19 -0
- opentrons/protocol_api/core/module.py +37 -4
- opentrons/protocol_api/core/protocol.py +11 -2
- opentrons/protocol_api/core/tasks.py +31 -0
- opentrons/protocol_api/core/well.py +4 -0
- opentrons/protocol_api/labware.py +5 -0
- opentrons/protocol_api/module_contexts.py +117 -11
- opentrons/protocol_api/protocol_context.py +26 -4
- opentrons/protocol_api/robot_context.py +38 -21
- opentrons/protocol_api/tasks.py +48 -0
- opentrons/protocol_api/validation.py +6 -1
- opentrons/protocol_engine/actions/__init__.py +4 -2
- opentrons/protocol_engine/actions/actions.py +22 -9
- opentrons/protocol_engine/clients/sync_client.py +42 -7
- opentrons/protocol_engine/commands/__init__.py +42 -0
- opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +2 -15
- opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +2 -15
- opentrons/protocol_engine/commands/aspirate.py +1 -0
- opentrons/protocol_engine/commands/command.py +1 -0
- opentrons/protocol_engine/commands/command_unions.py +49 -0
- opentrons/protocol_engine/commands/create_timer.py +83 -0
- opentrons/protocol_engine/commands/dispense.py +1 -0
- opentrons/protocol_engine/commands/drop_tip.py +32 -8
- opentrons/protocol_engine/commands/heater_shaker/__init__.py +14 -0
- opentrons/protocol_engine/commands/heater_shaker/common.py +20 -0
- opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +5 -4
- opentrons/protocol_engine/commands/heater_shaker/set_shake_speed.py +136 -0
- opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +31 -5
- opentrons/protocol_engine/commands/movement_common.py +2 -0
- opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
- opentrons/protocol_engine/commands/set_tip_state.py +97 -0
- opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +38 -7
- opentrons/protocol_engine/commands/thermocycler/__init__.py +16 -0
- opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +6 -0
- opentrons/protocol_engine/commands/thermocycler/run_profile.py +8 -0
- opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +40 -6
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +29 -5
- opentrons/protocol_engine/commands/thermocycler/start_run_extended_profile.py +191 -0
- opentrons/protocol_engine/commands/touch_tip.py +1 -1
- opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +6 -22
- opentrons/protocol_engine/commands/wait_for_tasks.py +98 -0
- opentrons/protocol_engine/errors/__init__.py +4 -0
- opentrons/protocol_engine/errors/exceptions.py +55 -0
- opentrons/protocol_engine/execution/__init__.py +2 -0
- opentrons/protocol_engine/execution/command_executor.py +8 -0
- opentrons/protocol_engine/execution/create_queue_worker.py +5 -1
- opentrons/protocol_engine/execution/labware_movement.py +9 -12
- opentrons/protocol_engine/execution/movement.py +2 -0
- opentrons/protocol_engine/execution/queue_worker.py +4 -0
- opentrons/protocol_engine/execution/run_control.py +8 -0
- opentrons/protocol_engine/execution/task_handler.py +157 -0
- opentrons/protocol_engine/protocol_engine.py +75 -34
- opentrons/protocol_engine/resources/__init__.py +2 -0
- opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
- opentrons/protocol_engine/resources/labware_validation.py +10 -6
- opentrons/protocol_engine/state/_well_math.py +60 -18
- opentrons/protocol_engine/state/addressable_areas.py +2 -0
- opentrons/protocol_engine/state/commands.py +14 -11
- opentrons/protocol_engine/state/geometry.py +213 -374
- opentrons/protocol_engine/state/labware.py +52 -102
- opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
- opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1331 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
- opentrons/protocol_engine/state/modules.py +21 -8
- opentrons/protocol_engine/state/motion.py +44 -0
- opentrons/protocol_engine/state/state.py +14 -0
- opentrons/protocol_engine/state/state_summary.py +2 -0
- opentrons/protocol_engine/state/tasks.py +139 -0
- opentrons/protocol_engine/state/tips.py +177 -258
- opentrons/protocol_engine/state/update_types.py +16 -9
- opentrons/protocol_engine/types/__init__.py +9 -3
- opentrons/protocol_engine/types/deck_configuration.py +5 -1
- opentrons/protocol_engine/types/instrument.py +8 -1
- opentrons/protocol_engine/types/labware.py +1 -13
- opentrons/protocol_engine/types/module.py +10 -0
- opentrons/protocol_engine/types/tasks.py +38 -0
- opentrons/protocol_engine/types/tip.py +9 -0
- opentrons/protocol_runner/create_simulating_orchestrator.py +29 -2
- opentrons/protocol_runner/run_orchestrator.py +18 -2
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/types.py +2 -1
- opentrons/simulate.py +48 -15
- opentrons/system/camera.py +1 -1
- {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/RECORD +143 -127
- opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
- {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a5.dist-info → opentrons-8.7.0a7.dist-info}/licenses/LICENSE +0 -0
|
@@ -24,6 +24,7 @@ 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,
|
|
27
28
|
InnerWellGeometry,
|
|
28
29
|
)
|
|
29
30
|
from opentrons_shared_data.deck.types import CutoutFixture
|
|
@@ -44,7 +45,6 @@ from ..errors.exceptions import (
|
|
|
44
45
|
)
|
|
45
46
|
from ..resources import (
|
|
46
47
|
fixture_validation,
|
|
47
|
-
labware_validation,
|
|
48
48
|
deck_configuration_provider,
|
|
49
49
|
)
|
|
50
50
|
from ..types import (
|
|
@@ -63,12 +63,10 @@ from ..types import (
|
|
|
63
63
|
ModuleLocation,
|
|
64
64
|
OnLabwareLocation,
|
|
65
65
|
LabwareLocation,
|
|
66
|
-
LabwareOffsetVector,
|
|
67
66
|
ModuleOffsetData,
|
|
68
67
|
CurrentWell,
|
|
69
68
|
CurrentPipetteLocation,
|
|
70
69
|
TipGeometry,
|
|
71
|
-
LabwareMovementOffsetData,
|
|
72
70
|
InStackerHopperLocation,
|
|
73
71
|
OnDeckLabwareLocation,
|
|
74
72
|
AddressableAreaLocation,
|
|
@@ -91,7 +89,7 @@ from ..types import (
|
|
|
91
89
|
labware_location_is_system,
|
|
92
90
|
WellLocationType,
|
|
93
91
|
WellLocationFunction,
|
|
94
|
-
|
|
92
|
+
GripperMoveType,
|
|
95
93
|
AddressableArea,
|
|
96
94
|
)
|
|
97
95
|
from ..types.liquid_level_detection import SimulatedProbeResult, LiquidTrackingType
|
|
@@ -108,8 +106,11 @@ from .inner_well_math_utils import (
|
|
|
108
106
|
find_volume_user_defined_volumes,
|
|
109
107
|
)
|
|
110
108
|
from ._well_math import wells_covered_by_pipette_configuration, nozzles_per_well
|
|
111
|
-
from .
|
|
112
|
-
|
|
109
|
+
from .labware_origin_math.stackup_origin_to_labware_origin import (
|
|
110
|
+
get_stackup_origin_to_labware_origin,
|
|
111
|
+
LabwareOriginContext,
|
|
112
|
+
LabwareStackupAncestorDefinition,
|
|
113
|
+
)
|
|
113
114
|
|
|
114
115
|
_LOG = getLogger(__name__)
|
|
115
116
|
SLOT_WIDTH = 128
|
|
@@ -125,13 +126,6 @@ class _TipDropSection(enum.Enum):
|
|
|
125
126
|
RIGHT = "right"
|
|
126
127
|
|
|
127
128
|
|
|
128
|
-
class _GripperMoveType(enum.Enum):
|
|
129
|
-
"""Types of gripper movement."""
|
|
130
|
-
|
|
131
|
-
PICK_UP_LABWARE = enum.auto()
|
|
132
|
-
DROP_LABWARE = enum.auto()
|
|
133
|
-
|
|
134
|
-
|
|
135
129
|
@dataclass
|
|
136
130
|
class _AbsoluteRobotExtents:
|
|
137
131
|
front_left: Dict[MountType, Point]
|
|
@@ -410,145 +404,114 @@ class GeometryView:
|
|
|
410
404
|
"""
|
|
411
405
|
location = self._labware.get(labware_id).location
|
|
412
406
|
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
|
+
)
|
|
413
423
|
|
|
414
|
-
slot_front_left = self.
|
|
415
|
-
stackup_origin_to_lw_origin =
|
|
416
|
-
|
|
424
|
+
slot_front_left = self._addressable_areas.get_addressable_area_position(aa_name)
|
|
425
|
+
stackup_origin_to_lw_origin = get_stackup_origin_to_labware_origin(
|
|
426
|
+
context=LabwareOriginContext.PIPETTING,
|
|
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,
|
|
417
432
|
)
|
|
418
433
|
module_cal_offset = self._get_calibrated_module_offset(location)
|
|
419
434
|
|
|
420
435
|
return slot_front_left + stackup_origin_to_lw_origin + module_cal_offset
|
|
421
436
|
|
|
422
|
-
def
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
)
|
|
427
|
-
parent_pos = self._addressable_areas.get_addressable_area_position(slot_name)
|
|
428
|
-
|
|
429
|
-
return parent_pos
|
|
430
|
-
|
|
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
|
-
)
|
|
457
|
-
)
|
|
458
|
-
|
|
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
|
-
)
|
|
437
|
+
def _get_stackup_lw_info_top_to_bottom(
|
|
438
|
+
self, labware_definition: LabwareDefinition, location: LabwareLocation
|
|
439
|
+
) -> list[tuple[LabwareDefinition, LabwareLocation]]:
|
|
440
|
+
"""Returns info about each labware in the stackup.
|
|
472
441
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
442
|
+
The list is ordered from the top labware to the bottom-most labware.
|
|
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
|
|
480
450
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
module_id=labware_location.moduleId,
|
|
485
|
-
)
|
|
451
|
+
while True:
|
|
452
|
+
definitions_locations_top_to_bottom.append(
|
|
453
|
+
(current_definition, current_location)
|
|
486
454
|
)
|
|
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}")
|
|
515
455
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
return self._addressable_areas.get_slot_definition(addressable_area_name)
|
|
456
|
+
if isinstance(current_location, OnLabwareLocation):
|
|
457
|
+
current_labware_id = current_location.labwareId
|
|
458
|
+
current_location = self._labware.get(current_labware_id).location
|
|
459
|
+
current_definition = self._labware.get_definition(current_labware_id)
|
|
460
|
+
else:
|
|
461
|
+
break
|
|
523
462
|
|
|
524
|
-
|
|
525
|
-
addressable_area_name = location.addressableAreaName
|
|
526
|
-
return self._addressable_areas.get_addressable_area(addressable_area_name)
|
|
463
|
+
return definitions_locations_top_to_bottom
|
|
527
464
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
465
|
+
def _get_stackup_module_parent_to_child_offset(
|
|
466
|
+
self, top_most_lw_location: LabwareLocation
|
|
467
|
+
) -> Union[Point, None]:
|
|
468
|
+
"""Traverse the stackup to find the first parent-to-child module offset, if any."""
|
|
469
|
+
current_location = top_most_lw_location
|
|
531
470
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
471
|
+
while True:
|
|
472
|
+
if isinstance(current_location, ModuleLocation):
|
|
473
|
+
module_parent_to_child_offset = (
|
|
474
|
+
self._modules.get_nominal_offset_to_child_from_addressable_area(
|
|
475
|
+
module_id=current_location.moduleId,
|
|
476
|
+
)
|
|
477
|
+
)
|
|
478
|
+
return module_parent_to_child_offset
|
|
535
479
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
480
|
+
if isinstance(current_location, OnLabwareLocation):
|
|
481
|
+
current_labware_id = current_location.labwareId
|
|
482
|
+
current_labware = self._labware.get(current_labware_id)
|
|
483
|
+
current_location = current_labware.location
|
|
484
|
+
else:
|
|
485
|
+
break
|
|
541
486
|
|
|
542
|
-
|
|
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
|
-
)
|
|
487
|
+
return None
|
|
547
488
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
489
|
+
def _get_stackup_underlying_ancestor_definition(
|
|
490
|
+
self, top_most_lw_location: LabwareLocation
|
|
491
|
+
) -> LabwareStackupAncestorDefinition:
|
|
492
|
+
"""Traverse the stackup to find the first non-labware definition."""
|
|
493
|
+
current_location = top_most_lw_location
|
|
494
|
+
|
|
495
|
+
while True:
|
|
496
|
+
if isinstance(current_location, OnLabwareLocation):
|
|
497
|
+
current_labware_id = current_location.labwareId
|
|
498
|
+
current_labware = self._labware.get(current_labware_id)
|
|
499
|
+
current_location = current_labware.location
|
|
500
|
+
else:
|
|
501
|
+
if isinstance(current_location, ModuleLocation):
|
|
502
|
+
return self._modules.get_definition(current_location.moduleId)
|
|
503
|
+
elif isinstance(current_location, AddressableAreaLocation):
|
|
504
|
+
return self._addressable_areas.get_addressable_area(
|
|
505
|
+
current_location.addressableAreaName
|
|
506
|
+
)
|
|
507
|
+
elif isinstance(current_location, DeckSlotLocation):
|
|
508
|
+
return self._addressable_areas.get_slot_definition(
|
|
509
|
+
current_location.slotName.id
|
|
510
|
+
)
|
|
511
|
+
else:
|
|
512
|
+
raise errors.InvalidLabwarePositionError(
|
|
513
|
+
f"Cannot get ancestor slot of location {current_location}"
|
|
514
|
+
)
|
|
552
515
|
|
|
553
516
|
def _get_underlying_addressable_area_name(self, location: LabwareLocation) -> str:
|
|
554
517
|
if isinstance(location, DeckSlotLocation):
|
|
@@ -776,7 +739,7 @@ class GeometryView:
|
|
|
776
739
|
|
|
777
740
|
if well_def.shape != "circular":
|
|
778
741
|
raise errors.LabwareIsNotTipRackError(
|
|
779
|
-
f"Well {well_name} in labware {labware_id} is not circular."
|
|
742
|
+
f"Well {well_name} in labware {self._labware.get_display_name(labware_id)} is not circular."
|
|
780
743
|
)
|
|
781
744
|
|
|
782
745
|
return TipGeometry(
|
|
@@ -867,7 +830,7 @@ class GeometryView:
|
|
|
867
830
|
slot_name = DeckSlotName.from_primitive(area_name)
|
|
868
831
|
elif labware.location == OFF_DECK_LOCATION:
|
|
869
832
|
raise errors.LabwareNotOnDeckError(
|
|
870
|
-
f"Labware {labware_id} does not have a slot associated with it"
|
|
833
|
+
f"Labware {self._labware.get_display_name(labware_id)} does not have a slot associated with it"
|
|
871
834
|
f" since it is no longer on the deck."
|
|
872
835
|
)
|
|
873
836
|
else:
|
|
@@ -1058,6 +1021,8 @@ class GeometryView:
|
|
|
1058
1021
|
location: Union[
|
|
1059
1022
|
DeckSlotLocation, ModuleLocation, OnLabwareLocation, AddressableAreaLocation
|
|
1060
1023
|
],
|
|
1024
|
+
move_type: GripperMoveType,
|
|
1025
|
+
user_additional_offset: Point | None,
|
|
1061
1026
|
) -> Point:
|
|
1062
1027
|
"""Get the grip point of the labware as placed on the given location.
|
|
1063
1028
|
|
|
@@ -1069,28 +1034,107 @@ class GeometryView:
|
|
|
1069
1034
|
It is calculated as the xy center of the slot with z as the point indicated by
|
|
1070
1035
|
z-position of labware bottom + grip height from labware bottom.
|
|
1071
1036
|
"""
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
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,
|
|
1075
1041
|
location=location,
|
|
1076
|
-
|
|
1077
|
-
is_topmost_labware=True, # We aren't concerned with entities above the gripped labware.
|
|
1042
|
+
move_type=move_type,
|
|
1078
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
|
+
grip_z_from_lw_origin = self._labware.get_grip_z(labware_definition)
|
|
1060
|
+
aa_name = self._get_underlying_addressable_area_name(location)
|
|
1079
1061
|
addressable_area = self._addressable_areas.get_addressable_area(aa_name)
|
|
1080
|
-
|
|
1081
|
-
labware_definition=labware_definition,
|
|
1062
|
+
stackup_defs_locs = self._get_stackup_lw_info_top_to_bottom(
|
|
1063
|
+
labware_definition=labware_definition, location=location
|
|
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
|
|
1070
|
+
)
|
|
1071
|
+
context_type = (
|
|
1072
|
+
LabwareOriginContext.GRIPPER_PICKING_UP
|
|
1073
|
+
if move_type == GripperMoveType.PICK_UP_LABWARE
|
|
1074
|
+
else LabwareOriginContext.GRIPPER_DROPPING
|
|
1082
1075
|
)
|
|
1083
|
-
mod_cal_offset = self._get_calibrated_module_offset(location)
|
|
1084
|
-
location_center = self._addressable_areas.get_addressable_area_center(aa_name)
|
|
1085
1076
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1077
|
+
aa_origin_to_lw_origin = get_stackup_origin_to_labware_origin(
|
|
1078
|
+
context=context_type,
|
|
1079
|
+
module_parent_to_child_offset=module_parent_to_child_offset,
|
|
1080
|
+
underlying_ancestor_definition=underlying_ancestor_def,
|
|
1081
|
+
stackup_lw_info_top_to_bottom=stackup_defs_locs,
|
|
1082
|
+
slot_name=addressable_area.base_slot,
|
|
1083
|
+
deck_definition=self._addressable_areas.deck_definition,
|
|
1092
1084
|
)
|
|
1093
1085
|
|
|
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
|
+
|
|
1094
1138
|
def _get_lw_origin_to_parent(
|
|
1095
1139
|
self, labware_definition: LabwareDefinition, addressable_area: AddressableArea
|
|
1096
1140
|
) -> Point:
|
|
@@ -1124,7 +1168,6 @@ class GeometryView:
|
|
|
1124
1168
|
if self._modules.should_dodge_thermocycler(
|
|
1125
1169
|
from_slot=from_slot, to_slot=to_slot
|
|
1126
1170
|
):
|
|
1127
|
-
|
|
1128
1171
|
middle_slot_fixture = (
|
|
1129
1172
|
self._addressable_areas.get_fixture_by_deck_slot_name(
|
|
1130
1173
|
DeckSlotName.SLOT_C2.to_equivalent_for_robot_type(
|
|
@@ -1393,41 +1436,6 @@ class GeometryView:
|
|
|
1393
1436
|
x_well_offset = 0
|
|
1394
1437
|
return x_well_offset
|
|
1395
1438
|
|
|
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
|
-
|
|
1431
1439
|
@staticmethod
|
|
1432
1440
|
def ensure_valid_gripper_location(
|
|
1433
1441
|
location: LabwareLocation,
|
|
@@ -1449,130 +1457,6 @@ class GeometryView:
|
|
|
1449
1457
|
)
|
|
1450
1458
|
return location
|
|
1451
1459
|
|
|
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
|
-
|
|
1576
1460
|
# todo(mm, 2024-11-05): This may be incorrect because it does not take the following
|
|
1577
1461
|
# offsets into account, which *are* taken into account for the actual gripper movement:
|
|
1578
1462
|
#
|
|
@@ -1624,64 +1508,6 @@ class GeometryView:
|
|
|
1624
1508
|
f"Cannot move labware '{labware_definition.parameters.loadName}' when {int(tip.volume)} µL tips are attached."
|
|
1625
1509
|
)
|
|
1626
1510
|
|
|
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
|
-
|
|
1685
1511
|
def get_location_sequence(self, labware_id: str) -> LabwareLocationSequence:
|
|
1686
1512
|
"""Provide the LocationSequence specifying the current position of the labware.
|
|
1687
1513
|
|
|
@@ -2041,6 +1867,15 @@ class GeometryView:
|
|
|
2041
1867
|
else:
|
|
2042
1868
|
return initial_handling_height
|
|
2043
1869
|
|
|
1870
|
+
def well_has_tracked_liquid(
|
|
1871
|
+
self,
|
|
1872
|
+
labware_id: str,
|
|
1873
|
+
well_name: str,
|
|
1874
|
+
) -> bool:
|
|
1875
|
+
"""Returns true if this well has had a liquid loaded or a probe result."""
|
|
1876
|
+
last_updated = self._wells.get_last_liquid_update(labware_id, well_name)
|
|
1877
|
+
return last_updated is not None
|
|
1878
|
+
|
|
2044
1879
|
def get_current_well_volume(
|
|
2045
1880
|
self,
|
|
2046
1881
|
labware_id: str,
|
|
@@ -2288,7 +2123,8 @@ class GeometryView:
|
|
|
2288
2123
|
except InvalidLiquidHeightFound as _exception:
|
|
2289
2124
|
raise InvalidLiquidHeightFound(
|
|
2290
2125
|
message=_exception.message
|
|
2291
|
-
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)}
|
|
2126
|
+
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)}"
|
|
2127
|
+
f" on slot {self.get_ancestor_slot_name(labware_id)}"
|
|
2292
2128
|
)
|
|
2293
2129
|
# if meniscus volume is a simulated value, comparisons aren't meaningful
|
|
2294
2130
|
if isinstance(meniscus_volume, SimulatedProbeResult):
|
|
@@ -2296,13 +2132,16 @@ class GeometryView:
|
|
|
2296
2132
|
remaining_volume = well_volumetric_capacity - meniscus_volume
|
|
2297
2133
|
if volume > remaining_volume:
|
|
2298
2134
|
raise errors.InvalidDispenseVolumeError(
|
|
2299
|
-
f"Attempting to dispense {volume}µL of liquid into a well that can currently only hold
|
|
2135
|
+
f"Attempting to dispense {volume}µL of liquid into a well that can currently only hold"
|
|
2136
|
+
f" {remaining_volume}µL (well {well_name} in labware {self._labware.get_display_name(labware_id)})"
|
|
2300
2137
|
)
|
|
2301
2138
|
else:
|
|
2302
2139
|
# TODO(pbm, 10-08-24): factor in well (LabwareStore) state volume
|
|
2303
2140
|
if volume > well_volumetric_capacity:
|
|
2304
2141
|
raise errors.InvalidDispenseVolumeError(
|
|
2305
|
-
f"Attempting to dispense {volume}µL of liquid into a well that can only hold
|
|
2142
|
+
f"Attempting to dispense {volume}µL of liquid into a well that can only hold"
|
|
2143
|
+
f" {well_volumetric_capacity}µL (well {well_name} in"
|
|
2144
|
+
f" labware {self._labware.get_display_name(labware_id)})"
|
|
2306
2145
|
)
|
|
2307
2146
|
|
|
2308
2147
|
def get_wells_covered_by_pipette_with_active_well(
|