opentrons 8.7.0a9__py3-none-any.whl → 8.8.0a8__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.
- opentrons/_version.py +2 -2
- opentrons/cli/analyze.py +4 -1
- opentrons/config/__init__.py +7 -0
- opentrons/drivers/asyncio/communication/serial_connection.py +126 -49
- 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/drivers/vacuum_module/__init__.py +5 -0
- opentrons/drivers/vacuum_module/abstract.py +93 -0
- opentrons/drivers/vacuum_module/driver.py +208 -0
- opentrons/drivers/vacuum_module/errors.py +39 -0
- opentrons/drivers/vacuum_module/simulator.py +85 -0
- opentrons/drivers/vacuum_module/types.py +79 -0
- opentrons/execute.py +3 -0
- opentrons/hardware_control/api.py +24 -5
- opentrons/hardware_control/backends/controller.py +8 -2
- opentrons/hardware_control/backends/flex_protocol.py +1 -0
- opentrons/hardware_control/backends/ot3controller.py +35 -2
- opentrons/hardware_control/backends/ot3simulator.py +3 -1
- opentrons/hardware_control/backends/ot3utils.py +37 -0
- 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 +105 -10
- 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 +14 -6
- 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/motion_utilities.py +6 -6
- opentrons/hardware_control/nozzle_manager.py +3 -0
- opentrons/hardware_control/ot3api.py +92 -17
- opentrons/hardware_control/poller.py +22 -8
- opentrons/hardware_control/protocols/liquid_handler.py +12 -4
- opentrons/hardware_control/scripts/update_module_fw.py +5 -0
- opentrons/hardware_control/types.py +43 -2
- opentrons/legacy_commands/commands.py +58 -5
- opentrons/legacy_commands/module_commands.py +52 -0
- opentrons/legacy_commands/protocol_commands.py +53 -1
- opentrons/legacy_commands/types.py +155 -1
- 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/_transfer_liquid_validation.py +17 -2
- opentrons/protocol_api/_types.py +8 -1
- opentrons/protocol_api/core/common.py +3 -1
- opentrons/protocol_api/core/engine/_default_labware_versions.py +33 -11
- opentrons/protocol_api/core/engine/deck_conflict.py +3 -1
- opentrons/protocol_api/core/engine/instrument.py +109 -26
- opentrons/protocol_api/core/engine/labware.py +8 -1
- opentrons/protocol_api/core/engine/module_core.py +95 -4
- opentrons/protocol_api/core/engine/pipette_movement_conflict.py +4 -18
- opentrons/protocol_api/core/engine/protocol.py +51 -2
- opentrons/protocol_api/core/engine/stringify.py +2 -0
- opentrons/protocol_api/core/engine/tasks.py +48 -0
- opentrons/protocol_api/core/engine/well.py +8 -0
- opentrons/protocol_api/core/instrument.py +19 -2
- opentrons/protocol_api/core/legacy/legacy_instrument_core.py +19 -2
- opentrons/protocol_api/core/legacy/legacy_module_core.py +33 -2
- opentrons/protocol_api/core/legacy/legacy_protocol_core.py +23 -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_instrument_core.py +19 -2
- 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 +58 -2
- opentrons/protocol_api/core/protocol.py +23 -2
- opentrons/protocol_api/core/tasks.py +31 -0
- opentrons/protocol_api/core/well.py +4 -0
- opentrons/protocol_api/instrument_context.py +388 -2
- opentrons/protocol_api/labware.py +10 -2
- opentrons/protocol_api/module_contexts.py +170 -6
- opentrons/protocol_api/protocol_context.py +87 -21
- opentrons/protocol_api/robot_context.py +41 -25
- opentrons/protocol_api/tasks.py +48 -0
- opentrons/protocol_api/validation.py +49 -3
- opentrons/protocol_engine/__init__.py +4 -0
- opentrons/protocol_engine/actions/__init__.py +6 -2
- opentrons/protocol_engine/actions/actions.py +31 -9
- opentrons/protocol_engine/clients/sync_client.py +42 -7
- opentrons/protocol_engine/commands/__init__.py +56 -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/absorbance_reader/read.py +22 -23
- opentrons/protocol_engine/commands/aspirate.py +1 -0
- opentrons/protocol_engine/commands/aspirate_while_tracking.py +52 -19
- opentrons/protocol_engine/commands/capture_image.py +302 -0
- opentrons/protocol_engine/commands/command.py +2 -0
- opentrons/protocol_engine/commands/command_unions.py +62 -0
- opentrons/protocol_engine/commands/create_timer.py +83 -0
- opentrons/protocol_engine/commands/dispense.py +1 -0
- opentrons/protocol_engine/commands/dispense_while_tracking.py +56 -19
- opentrons/protocol_engine/commands/drop_tip.py +32 -8
- opentrons/protocol_engine/commands/flex_stacker/common.py +35 -0
- opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +7 -0
- 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/move_labware.py +3 -4
- opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +1 -1
- opentrons/protocol_engine/commands/movement_common.py +31 -2
- opentrons/protocol_engine/commands/pick_up_tip.py +21 -11
- opentrons/protocol_engine/commands/pipetting_common.py +48 -3
- 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 +44 -7
- opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +43 -14
- 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/create_protocol_engine.py +12 -0
- opentrons/protocol_engine/engine_support.py +3 -0
- opentrons/protocol_engine/errors/__init__.py +12 -0
- opentrons/protocol_engine/errors/exceptions.py +119 -0
- opentrons/protocol_engine/execution/__init__.py +4 -0
- opentrons/protocol_engine/execution/command_executor.py +62 -1
- opentrons/protocol_engine/execution/create_queue_worker.py +9 -2
- opentrons/protocol_engine/execution/labware_movement.py +13 -15
- opentrons/protocol_engine/execution/movement.py +2 -0
- opentrons/protocol_engine/execution/pipetting.py +26 -25
- 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 +137 -36
- opentrons/protocol_engine/resources/__init__.py +4 -0
- opentrons/protocol_engine/resources/camera_provider.py +110 -0
- opentrons/protocol_engine/resources/concurrency_provider.py +27 -0
- opentrons/protocol_engine/resources/deck_configuration_provider.py +7 -0
- opentrons/protocol_engine/resources/file_provider.py +133 -58
- opentrons/protocol_engine/resources/labware_validation.py +10 -6
- opentrons/protocol_engine/slot_standardization.py +2 -0
- opentrons/protocol_engine/state/_well_math.py +60 -18
- opentrons/protocol_engine/state/addressable_areas.py +2 -0
- opentrons/protocol_engine/state/camera.py +54 -0
- opentrons/protocol_engine/state/commands.py +37 -14
- opentrons/protocol_engine/state/geometry.py +276 -379
- opentrons/protocol_engine/state/labware.py +62 -108
- opentrons/protocol_engine/state/labware_origin_math/errors.py +94 -0
- opentrons/protocol_engine/state/labware_origin_math/stackup_origin_to_labware_origin.py +1336 -0
- opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +37 -0
- opentrons/protocol_engine/state/modules.py +30 -8
- opentrons/protocol_engine/state/motion.py +60 -18
- opentrons/protocol_engine/state/preconditions.py +59 -0
- opentrons/protocol_engine/state/state.py +44 -0
- opentrons/protocol_engine/state/state_summary.py +4 -0
- opentrons/protocol_engine/state/tasks.py +139 -0
- opentrons/protocol_engine/state/tips.py +177 -258
- opentrons/protocol_engine/state/update_types.py +26 -9
- opentrons/protocol_engine/types/__init__.py +23 -4
- opentrons/protocol_engine/types/command_preconditions.py +18 -0
- 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/location.py +26 -2
- opentrons/protocol_engine/types/module.py +11 -1
- 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/protocol_runner.py +14 -1
- opentrons/protocol_runner/run_orchestrator.py +49 -2
- opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +2 -2
- opentrons/protocols/api_support/definitions.py +1 -1
- opentrons/protocols/api_support/types.py +2 -1
- opentrons/simulate.py +51 -15
- opentrons/system/camera.py +334 -4
- opentrons/system/ffmpeg.py +110 -0
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/METADATA +4 -4
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/RECORD +189 -161
- opentrons/protocol_engine/state/_labware_origin_math.py +0 -636
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/WHEEL +0 -0
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.dist-info}/entry_points.txt +0 -0
- {opentrons-8.7.0a9.dist-info → opentrons-8.8.0a8.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,13 +63,13 @@ 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,
|
|
71
|
+
WASTE_CHUTE_LOCATION,
|
|
72
|
+
AccessibleByGripperLocation,
|
|
73
73
|
OnDeckLabwareLocation,
|
|
74
74
|
AddressableAreaLocation,
|
|
75
75
|
AddressableOffsetVector,
|
|
@@ -91,7 +91,7 @@ from ..types import (
|
|
|
91
91
|
labware_location_is_system,
|
|
92
92
|
WellLocationType,
|
|
93
93
|
WellLocationFunction,
|
|
94
|
-
|
|
94
|
+
GripperMoveType,
|
|
95
95
|
AddressableArea,
|
|
96
96
|
)
|
|
97
97
|
from ..types.liquid_level_detection import SimulatedProbeResult, LiquidTrackingType
|
|
@@ -108,8 +108,11 @@ from .inner_well_math_utils import (
|
|
|
108
108
|
find_volume_user_defined_volumes,
|
|
109
109
|
)
|
|
110
110
|
from ._well_math import wells_covered_by_pipette_configuration, nozzles_per_well
|
|
111
|
-
from .
|
|
112
|
-
|
|
111
|
+
from .labware_origin_math.stackup_origin_to_labware_origin import (
|
|
112
|
+
get_stackup_origin_to_labware_origin,
|
|
113
|
+
LabwareOriginContext,
|
|
114
|
+
LabwareStackupAncestorDefinition,
|
|
115
|
+
)
|
|
113
116
|
|
|
114
117
|
_LOG = getLogger(__name__)
|
|
115
118
|
SLOT_WIDTH = 128
|
|
@@ -125,13 +128,6 @@ class _TipDropSection(enum.Enum):
|
|
|
125
128
|
RIGHT = "right"
|
|
126
129
|
|
|
127
130
|
|
|
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
131
|
@dataclass
|
|
136
132
|
class _AbsoluteRobotExtents:
|
|
137
133
|
front_left: Dict[MountType, Point]
|
|
@@ -204,6 +200,7 @@ class GeometryView:
|
|
|
204
200
|
if isinstance(loc, InStackerHopperLocation) or isinstance(
|
|
205
201
|
loc, NotOnDeckLocationSequenceComponent
|
|
206
202
|
):
|
|
203
|
+
|
|
207
204
|
return False
|
|
208
205
|
return True
|
|
209
206
|
|
|
@@ -402,6 +399,11 @@ class GeometryView:
|
|
|
402
399
|
"Labware does not have a slot or module associated with it"
|
|
403
400
|
" since it is no longer on the deck."
|
|
404
401
|
)
|
|
402
|
+
elif location == WASTE_CHUTE_LOCATION:
|
|
403
|
+
raise errors.LabwareNotOnDeckError(
|
|
404
|
+
"Labware does not have a slot or module associated with it"
|
|
405
|
+
" since it is in the waste chute."
|
|
406
|
+
)
|
|
405
407
|
|
|
406
408
|
def get_labware_origin_position(self, labware_id: str) -> Point:
|
|
407
409
|
"""Get the deck coordinates of a labware's origin.
|
|
@@ -410,145 +412,117 @@ class GeometryView:
|
|
|
410
412
|
"""
|
|
411
413
|
location = self._labware.get(labware_id).location
|
|
412
414
|
definition = self._labware.get_definition(labware_id)
|
|
415
|
+
aa_name = self._get_underlying_addressable_area_name(location)
|
|
416
|
+
# TODO(jh, 08-18-25): Labware locations return the underlying slot as the "on location" for the fixed trash,
|
|
417
|
+
# but the underlying slot's name does not exist in addressable area state. Getting the addressable area from data is
|
|
418
|
+
# a workaround. Investigate further.
|
|
419
|
+
addressable_area = self._addressable_areas._get_addressable_area_from_deck_data(
|
|
420
|
+
aa_name, do_compatibility_check=False
|
|
421
|
+
)
|
|
422
|
+
stackup_lw_defs_locs = self._get_stackup_lw_info_top_to_bottom(
|
|
423
|
+
labware_definition=definition, location=location
|
|
424
|
+
)
|
|
425
|
+
underlying_ancestor_def = self._get_stackup_underlying_ancestor_definition(
|
|
426
|
+
location
|
|
427
|
+
)
|
|
428
|
+
module_parent_to_child_offset = self._get_stackup_module_parent_to_child_offset(
|
|
429
|
+
location
|
|
430
|
+
)
|
|
413
431
|
|
|
414
|
-
slot_front_left = self.
|
|
415
|
-
stackup_origin_to_lw_origin =
|
|
416
|
-
|
|
432
|
+
slot_front_left = self._addressable_areas.get_addressable_area_position(aa_name)
|
|
433
|
+
stackup_origin_to_lw_origin = get_stackup_origin_to_labware_origin(
|
|
434
|
+
context=LabwareOriginContext.PIPETTING,
|
|
435
|
+
stackup_lw_info_top_to_bottom=stackup_lw_defs_locs,
|
|
436
|
+
underlying_ancestor_definition=underlying_ancestor_def,
|
|
437
|
+
module_parent_to_child_offset=module_parent_to_child_offset,
|
|
438
|
+
deck_definition=self._addressable_areas.deck_definition,
|
|
439
|
+
slot_name=addressable_area.base_slot,
|
|
417
440
|
)
|
|
418
441
|
module_cal_offset = self._get_calibrated_module_offset(location)
|
|
419
442
|
|
|
420
443
|
return slot_front_left + stackup_origin_to_lw_origin + module_cal_offset
|
|
421
444
|
|
|
422
|
-
def
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
)
|
|
427
|
-
parent_pos = self._addressable_areas.get_addressable_area_position(slot_name)
|
|
445
|
+
def _get_stackup_lw_info_top_to_bottom(
|
|
446
|
+
self, labware_definition: LabwareDefinition, location: LabwareLocation
|
|
447
|
+
) -> list[tuple[LabwareDefinition, LabwareLocation]]:
|
|
448
|
+
"""Returns info about each labware in the stackup.
|
|
428
449
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
)
|
|
450
|
+
The list is ordered from the top labware to the bottom-most labware.
|
|
451
|
+
The first entry will always be the definition and location of the given labware itself.
|
|
452
|
+
"""
|
|
453
|
+
definitions_locations_top_to_bottom: list[
|
|
454
|
+
tuple[LabwareDefinition, LabwareLocation]
|
|
455
|
+
] = []
|
|
456
|
+
current_location = location
|
|
457
|
+
current_definition = labware_definition
|
|
458
458
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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."
|
|
459
|
+
while True:
|
|
460
|
+
definitions_locations_top_to_bottom.append(
|
|
461
|
+
(current_definition, current_location)
|
|
471
462
|
)
|
|
472
463
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
464
|
+
if isinstance(current_location, OnLabwareLocation):
|
|
465
|
+
current_labware_id = current_location.labwareId
|
|
466
|
+
current_location = self._labware.get(current_labware_id).location
|
|
467
|
+
current_definition = self._labware.get_definition(current_labware_id)
|
|
468
|
+
else:
|
|
469
|
+
break
|
|
470
|
+
return definitions_locations_top_to_bottom
|
|
471
|
+
|
|
472
|
+
def _get_stackup_module_parent_to_child_offset(
|
|
473
|
+
self, top_most_lw_location: LabwareLocation
|
|
474
|
+
) -> Union[Point, None]:
|
|
475
|
+
"""Traverse the stackup to find the first parent-to-child module offset, if any."""
|
|
476
|
+
current_location = top_most_lw_location
|
|
477
|
+
|
|
478
|
+
while True:
|
|
479
|
+
if isinstance(current_location, ModuleLocation):
|
|
480
|
+
module_parent_to_child_offset = (
|
|
481
|
+
self._modules.get_nominal_offset_to_child_from_addressable_area(
|
|
482
|
+
module_id=current_location.moduleId,
|
|
483
|
+
)
|
|
485
484
|
)
|
|
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}")
|
|
515
|
-
|
|
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)
|
|
523
|
-
|
|
524
|
-
elif isinstance(location, AddressableAreaLocation):
|
|
525
|
-
addressable_area_name = location.addressableAreaName
|
|
526
|
-
return self._addressable_areas.get_addressable_area(addressable_area_name)
|
|
485
|
+
return module_parent_to_child_offset
|
|
527
486
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return self._labware.get_definition(below_labware_id)
|
|
487
|
+
if isinstance(current_location, OnLabwareLocation):
|
|
488
|
+
current_labware_id = current_location.labwareId
|
|
489
|
+
current_labware = self._labware.get(current_labware_id)
|
|
490
|
+
current_location = current_labware.location
|
|
491
|
+
else:
|
|
492
|
+
break
|
|
535
493
|
|
|
536
|
-
|
|
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
|
-
)
|
|
494
|
+
return None
|
|
541
495
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
496
|
+
def _get_stackup_underlying_ancestor_definition(
|
|
497
|
+
self, top_most_lw_location: LabwareLocation
|
|
498
|
+
) -> LabwareStackupAncestorDefinition:
|
|
499
|
+
"""Traverse the stackup to find the first non-labware definition."""
|
|
500
|
+
current_location = top_most_lw_location
|
|
501
|
+
while True:
|
|
502
|
+
if isinstance(current_location, OnLabwareLocation):
|
|
503
|
+
current_labware_id = current_location.labwareId
|
|
504
|
+
current_labware = self._labware.get(current_labware_id)
|
|
505
|
+
current_location = current_labware.location
|
|
506
|
+
else:
|
|
507
|
+
if isinstance(current_location, ModuleLocation):
|
|
508
|
+
return self._modules.get_definition(current_location.moduleId)
|
|
547
509
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
510
|
+
elif isinstance(current_location, AddressableAreaLocation):
|
|
511
|
+
return self._addressable_areas.get_addressable_area(
|
|
512
|
+
current_location.addressableAreaName
|
|
513
|
+
)
|
|
514
|
+
elif isinstance(current_location, DeckSlotLocation):
|
|
515
|
+
return self._addressable_areas.get_slot_definition(
|
|
516
|
+
current_location.slotName.id
|
|
517
|
+
)
|
|
518
|
+
elif current_location == WASTE_CHUTE_LOCATION:
|
|
519
|
+
return self._addressable_areas.get_addressable_area(
|
|
520
|
+
"gripperWasteChute"
|
|
521
|
+
)
|
|
522
|
+
else:
|
|
523
|
+
raise errors.InvalidLabwarePositionError(
|
|
524
|
+
f"Cannot get ancestor slot of location {current_location}"
|
|
525
|
+
)
|
|
552
526
|
|
|
553
527
|
def _get_underlying_addressable_area_name(self, location: LabwareLocation) -> str:
|
|
554
528
|
if isinstance(location, DeckSlotLocation):
|
|
@@ -559,6 +533,8 @@ class GeometryView:
|
|
|
559
533
|
return self._modules.get_provided_addressable_area(location.moduleId)
|
|
560
534
|
elif isinstance(location, OnLabwareLocation):
|
|
561
535
|
return self.get_ancestor_addressable_area_name(location.labwareId)
|
|
536
|
+
elif location == WASTE_CHUTE_LOCATION:
|
|
537
|
+
return "gripperWasteChute"
|
|
562
538
|
else:
|
|
563
539
|
raise errors.InvalidLabwarePositionError(
|
|
564
540
|
f"Cannot get ancestor slot of location {location}"
|
|
@@ -661,7 +637,9 @@ class GeometryView:
|
|
|
661
637
|
if meniscus_tracking:
|
|
662
638
|
location = LiquidHandlingWellLocation(
|
|
663
639
|
origin=WellOrigin.MENISCUS,
|
|
664
|
-
offset=WellOffset(
|
|
640
|
+
offset=WellOffset(
|
|
641
|
+
x=absolute_point.x, y=absolute_point.y, z=absolute_point.z
|
|
642
|
+
),
|
|
665
643
|
)
|
|
666
644
|
# TODO(cm): handle operationVolume being a float other than 0
|
|
667
645
|
if meniscus_tracking == MeniscusTrackingTarget.END:
|
|
@@ -717,6 +695,9 @@ class GeometryView:
|
|
|
717
695
|
return well_def.depth
|
|
718
696
|
|
|
719
697
|
def _get_highest_z_from_labware_data(self, lw_data: LoadedLabware) -> float:
|
|
698
|
+
if lw_data.location == WASTE_CHUTE_LOCATION:
|
|
699
|
+
# Returns 0 so that the waste chute height is not added to the height of the lbw
|
|
700
|
+
return 0
|
|
720
701
|
labware_pos = self.get_labware_position(lw_data.id)
|
|
721
702
|
z_dim = self._labware.get_dimensions(labware_id=lw_data.id).z
|
|
722
703
|
height_over_labware: float = 0
|
|
@@ -776,7 +757,7 @@ class GeometryView:
|
|
|
776
757
|
|
|
777
758
|
if well_def.shape != "circular":
|
|
778
759
|
raise errors.LabwareIsNotTipRackError(
|
|
779
|
-
f"Well {well_name} in labware {labware_id} is not circular."
|
|
760
|
+
f"Well {well_name} in labware {self._labware.get_display_name(labware_id)} is not circular."
|
|
780
761
|
)
|
|
781
762
|
|
|
782
763
|
return TipGeometry(
|
|
@@ -867,9 +848,14 @@ class GeometryView:
|
|
|
867
848
|
slot_name = DeckSlotName.from_primitive(area_name)
|
|
868
849
|
elif labware.location == OFF_DECK_LOCATION:
|
|
869
850
|
raise errors.LabwareNotOnDeckError(
|
|
870
|
-
f"Labware {labware_id} does not have a slot associated with it"
|
|
851
|
+
f"Labware {self._labware.get_display_name(labware_id)} does not have a slot associated with it"
|
|
871
852
|
f" since it is no longer on the deck."
|
|
872
853
|
)
|
|
854
|
+
elif labware.location == WASTE_CHUTE_LOCATION:
|
|
855
|
+
raise errors.LabwareNotOnDeckError(
|
|
856
|
+
f"Labware {self._labware.get_display_name(labware_id)} does not have a slot associated with it"
|
|
857
|
+
f" since it is in the waste chute."
|
|
858
|
+
)
|
|
873
859
|
else:
|
|
874
860
|
_LOG.error(
|
|
875
861
|
f"Unhandled location type in get_ancestor_slot_name: {labware.location}"
|
|
@@ -1055,9 +1041,9 @@ class GeometryView:
|
|
|
1055
1041
|
def get_labware_grip_point(
|
|
1056
1042
|
self,
|
|
1057
1043
|
labware_definition: LabwareDefinition,
|
|
1058
|
-
location:
|
|
1059
|
-
|
|
1060
|
-
|
|
1044
|
+
location: AccessibleByGripperLocation,
|
|
1045
|
+
move_type: GripperMoveType,
|
|
1046
|
+
user_additional_offset: Point | None,
|
|
1061
1047
|
) -> Point:
|
|
1062
1048
|
"""Get the grip point of the labware as placed on the given location.
|
|
1063
1049
|
|
|
@@ -1069,28 +1055,105 @@ class GeometryView:
|
|
|
1069
1055
|
It is calculated as the xy center of the slot with z as the point indicated by
|
|
1070
1056
|
z-position of labware bottom + grip height from labware bottom.
|
|
1071
1057
|
"""
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1058
|
+
mod_cal_offset = self._get_calibrated_module_offset(location)
|
|
1059
|
+
user_additional_offset = user_additional_offset or Point()
|
|
1060
|
+
aa_origin_to_nominal_grip_point = self._get_aa_origin_to_nominal_grip_point(
|
|
1061
|
+
labware_definition=labware_definition,
|
|
1075
1062
|
location=location,
|
|
1076
|
-
|
|
1077
|
-
is_topmost_labware=True, # We aren't concerned with entities above the gripped labware.
|
|
1063
|
+
move_type=move_type,
|
|
1078
1064
|
)
|
|
1065
|
+
|
|
1066
|
+
return aa_origin_to_nominal_grip_point + mod_cal_offset + user_additional_offset
|
|
1067
|
+
|
|
1068
|
+
def _get_aa_origin_to_nominal_grip_point(
|
|
1069
|
+
self,
|
|
1070
|
+
labware_definition: LabwareDefinition,
|
|
1071
|
+
location: AccessibleByGripperLocation,
|
|
1072
|
+
move_type: GripperMoveType,
|
|
1073
|
+
) -> Point:
|
|
1074
|
+
"""Get the nominal grip point of a labware.
|
|
1075
|
+
|
|
1076
|
+
Does not include module calibration offsets or user additional offsets.
|
|
1077
|
+
"""
|
|
1078
|
+
grip_z_from_lw_origin = self._labware.get_grip_z(labware_definition)
|
|
1079
|
+
aa_name = self._get_underlying_addressable_area_name(location)
|
|
1079
1080
|
addressable_area = self._addressable_areas.get_addressable_area(aa_name)
|
|
1080
|
-
|
|
1081
|
-
labware_definition=labware_definition,
|
|
1081
|
+
stackup_defs_locs = self._get_stackup_lw_info_top_to_bottom(
|
|
1082
|
+
labware_definition=labware_definition, location=location
|
|
1083
|
+
)
|
|
1084
|
+
module_parent_to_child_offset = self._get_stackup_module_parent_to_child_offset(
|
|
1085
|
+
location
|
|
1086
|
+
)
|
|
1087
|
+
underlying_ancestor_def = self._get_stackup_underlying_ancestor_definition(
|
|
1088
|
+
location
|
|
1089
|
+
)
|
|
1090
|
+
context_type = (
|
|
1091
|
+
LabwareOriginContext.GRIPPER_PICKING_UP
|
|
1092
|
+
if move_type == GripperMoveType.PICK_UP_LABWARE
|
|
1093
|
+
else LabwareOriginContext.GRIPPER_DROPPING
|
|
1082
1094
|
)
|
|
1083
|
-
mod_cal_offset = self._get_calibrated_module_offset(location)
|
|
1084
|
-
location_center = self._addressable_areas.get_addressable_area_center(aa_name)
|
|
1085
1095
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1096
|
+
aa_origin_to_lw_origin = get_stackup_origin_to_labware_origin(
|
|
1097
|
+
context=context_type,
|
|
1098
|
+
module_parent_to_child_offset=module_parent_to_child_offset,
|
|
1099
|
+
underlying_ancestor_definition=underlying_ancestor_def,
|
|
1100
|
+
stackup_lw_info_top_to_bottom=stackup_defs_locs,
|
|
1101
|
+
slot_name=addressable_area.base_slot,
|
|
1102
|
+
deck_definition=self._addressable_areas.deck_definition,
|
|
1092
1103
|
)
|
|
1093
1104
|
|
|
1105
|
+
if isinstance(labware_definition, LabwareDefinition2):
|
|
1106
|
+
lw_origin_to_aa_origin = self._get_lw_origin_to_parent(
|
|
1107
|
+
labware_definition=labware_definition, addressable_area=addressable_area
|
|
1108
|
+
)
|
|
1109
|
+
aa_origin_to_aa_center = (
|
|
1110
|
+
self._addressable_areas.get_addressable_area_center(aa_name)
|
|
1111
|
+
)
|
|
1112
|
+
aa_center_to_nominal_grip_point = Point(0, 0, grip_z_from_lw_origin)
|
|
1113
|
+
|
|
1114
|
+
return (
|
|
1115
|
+
aa_origin_to_lw_origin
|
|
1116
|
+
+ lw_origin_to_aa_origin
|
|
1117
|
+
+ aa_origin_to_aa_center
|
|
1118
|
+
+ aa_center_to_nominal_grip_point
|
|
1119
|
+
)
|
|
1120
|
+
|
|
1121
|
+
else:
|
|
1122
|
+
assert isinstance(labware_definition, LabwareDefinition3)
|
|
1123
|
+
|
|
1124
|
+
aa_origin = self._addressable_areas.get_addressable_area_position(aa_name)
|
|
1125
|
+
lw_origin_to_lw_center = self._get_lw_origin_to_lw_center(
|
|
1126
|
+
labware_definition
|
|
1127
|
+
)
|
|
1128
|
+
lw_origin_to_lw_grip_center = Point(
|
|
1129
|
+
x=lw_origin_to_lw_center.x,
|
|
1130
|
+
y=lw_origin_to_lw_center.y,
|
|
1131
|
+
z=grip_z_from_lw_origin,
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
return aa_origin + aa_origin_to_lw_origin + lw_origin_to_lw_grip_center
|
|
1135
|
+
|
|
1136
|
+
def _get_lw_origin_to_lw_center(
|
|
1137
|
+
self, labware_definition: LabwareDefinition
|
|
1138
|
+
) -> Point:
|
|
1139
|
+
"""Get the x,y,z center of the labware."""
|
|
1140
|
+
if isinstance(labware_definition, LabwareDefinition2):
|
|
1141
|
+
dimensions = labware_definition.dimensions
|
|
1142
|
+
x = dimensions.xDimension / 2
|
|
1143
|
+
y = dimensions.yDimension / 2
|
|
1144
|
+
z = dimensions.zDimension / 2
|
|
1145
|
+
|
|
1146
|
+
return Point(x, y, z)
|
|
1147
|
+
else:
|
|
1148
|
+
front_right_top = labware_definition.extents.total.frontRightTop
|
|
1149
|
+
back_left_bottom = labware_definition.extents.total.backLeftBottom
|
|
1150
|
+
|
|
1151
|
+
x = (front_right_top.x - back_left_bottom.x) / 2
|
|
1152
|
+
y = (front_right_top.y - back_left_bottom.y) / 2
|
|
1153
|
+
z = (front_right_top.z - back_left_bottom.z) / 2
|
|
1154
|
+
|
|
1155
|
+
return Point(x, y, z)
|
|
1156
|
+
|
|
1094
1157
|
def _get_lw_origin_to_parent(
|
|
1095
1158
|
self, labware_definition: LabwareDefinition, addressable_area: AddressableArea
|
|
1096
1159
|
) -> Point:
|
|
@@ -1124,7 +1187,6 @@ class GeometryView:
|
|
|
1124
1187
|
if self._modules.should_dodge_thermocycler(
|
|
1125
1188
|
from_slot=from_slot, to_slot=to_slot
|
|
1126
1189
|
):
|
|
1127
|
-
|
|
1128
1190
|
middle_slot_fixture = (
|
|
1129
1191
|
self._addressable_areas.get_fixture_by_deck_slot_name(
|
|
1130
1192
|
DeckSlotName.SLOT_C2.to_equivalent_for_robot_type(
|
|
@@ -1393,48 +1455,20 @@ class GeometryView:
|
|
|
1393
1455
|
x_well_offset = 0
|
|
1394
1456
|
return x_well_offset
|
|
1395
1457
|
|
|
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
1458
|
@staticmethod
|
|
1432
1459
|
def ensure_valid_gripper_location(
|
|
1433
1460
|
location: LabwareLocation,
|
|
1434
1461
|
) -> Union[
|
|
1435
|
-
DeckSlotLocation,
|
|
1462
|
+
DeckSlotLocation,
|
|
1463
|
+
ModuleLocation,
|
|
1464
|
+
OnLabwareLocation,
|
|
1465
|
+
AddressableAreaLocation,
|
|
1436
1466
|
]:
|
|
1437
1467
|
"""Ensure valid on-deck location for gripper, otherwise raise error."""
|
|
1468
|
+
if location == WASTE_CHUTE_LOCATION:
|
|
1469
|
+
raise errors.LabwareMovementNotAllowedError(
|
|
1470
|
+
"Labware movements out of the waste chute are not supported using the gripper."
|
|
1471
|
+
)
|
|
1438
1472
|
if not isinstance(
|
|
1439
1473
|
location,
|
|
1440
1474
|
(
|
|
@@ -1449,129 +1483,27 @@ class GeometryView:
|
|
|
1449
1483
|
)
|
|
1450
1484
|
return location
|
|
1451
1485
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
location:
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
)
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|
-
)
|
|
1486
|
+
@staticmethod
|
|
1487
|
+
def ensure_valid_new_gripper_location(
|
|
1488
|
+
location: LabwareLocation,
|
|
1489
|
+
) -> AccessibleByGripperLocation:
|
|
1490
|
+
"""Ensure valid on-deck location for gripper, otherwise raise error."""
|
|
1491
|
+
if (
|
|
1492
|
+
not isinstance(
|
|
1493
|
+
location,
|
|
1494
|
+
(
|
|
1495
|
+
DeckSlotLocation,
|
|
1496
|
+
ModuleLocation,
|
|
1497
|
+
OnLabwareLocation,
|
|
1498
|
+
AddressableAreaLocation,
|
|
1499
|
+
),
|
|
1500
|
+
)
|
|
1501
|
+
and location != WASTE_CHUTE_LOCATION
|
|
1502
|
+
):
|
|
1503
|
+
raise errors.LabwareMovementNotAllowedError(
|
|
1504
|
+
"Off-deck labware movements are not supported using the gripper."
|
|
1505
|
+
)
|
|
1506
|
+
return location
|
|
1575
1507
|
|
|
1576
1508
|
# todo(mm, 2024-11-05): This may be incorrect because it does not take the following
|
|
1577
1509
|
# offsets into account, which *are* taken into account for the actual gripper movement:
|
|
@@ -1624,64 +1556,6 @@ class GeometryView:
|
|
|
1624
1556
|
f"Cannot move labware '{labware_definition.parameters.loadName}' when {int(tip.volume)} µL tips are attached."
|
|
1625
1557
|
)
|
|
1626
1558
|
|
|
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
1559
|
def get_location_sequence(self, labware_id: str) -> LabwareLocationSequence:
|
|
1686
1560
|
"""Provide the LocationSequence specifying the current position of the labware.
|
|
1687
1561
|
|
|
@@ -1851,6 +1725,12 @@ class GeometryView:
|
|
|
1851
1725
|
return self._recurse_labware_location_from_stacker_hopper(
|
|
1852
1726
|
labware_location, building
|
|
1853
1727
|
)
|
|
1728
|
+
elif labware_location == WASTE_CHUTE_LOCATION:
|
|
1729
|
+
return [
|
|
1730
|
+
NotOnDeckLocationSequenceComponent(
|
|
1731
|
+
logicalLocationName=WASTE_CHUTE_LOCATION
|
|
1732
|
+
)
|
|
1733
|
+
]
|
|
1854
1734
|
else:
|
|
1855
1735
|
_LOG.warn(f"Unhandled labware location kind: {labware_location}")
|
|
1856
1736
|
return building
|
|
@@ -2041,6 +1921,15 @@ class GeometryView:
|
|
|
2041
1921
|
else:
|
|
2042
1922
|
return initial_handling_height
|
|
2043
1923
|
|
|
1924
|
+
def well_has_tracked_liquid(
|
|
1925
|
+
self,
|
|
1926
|
+
labware_id: str,
|
|
1927
|
+
well_name: str,
|
|
1928
|
+
) -> bool:
|
|
1929
|
+
"""Returns true if this well has had a liquid loaded or a probe result."""
|
|
1930
|
+
last_updated = self._wells.get_last_liquid_update(labware_id, well_name)
|
|
1931
|
+
return last_updated is not None
|
|
1932
|
+
|
|
2044
1933
|
def get_current_well_volume(
|
|
2045
1934
|
self,
|
|
2046
1935
|
labware_id: str,
|
|
@@ -2288,7 +2177,8 @@ class GeometryView:
|
|
|
2288
2177
|
except InvalidLiquidHeightFound as _exception:
|
|
2289
2178
|
raise InvalidLiquidHeightFound(
|
|
2290
2179
|
message=_exception.message
|
|
2291
|
-
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)}
|
|
2180
|
+
+ f"for well {well_name} of {self._labware.get_display_name(labware_id)}"
|
|
2181
|
+
f" on slot {self.get_ancestor_slot_name(labware_id)}"
|
|
2292
2182
|
)
|
|
2293
2183
|
# if meniscus volume is a simulated value, comparisons aren't meaningful
|
|
2294
2184
|
if isinstance(meniscus_volume, SimulatedProbeResult):
|
|
@@ -2296,13 +2186,16 @@ class GeometryView:
|
|
|
2296
2186
|
remaining_volume = well_volumetric_capacity - meniscus_volume
|
|
2297
2187
|
if volume > remaining_volume:
|
|
2298
2188
|
raise errors.InvalidDispenseVolumeError(
|
|
2299
|
-
f"Attempting to dispense {volume}µL of liquid into a well that can currently only hold
|
|
2189
|
+
f"Attempting to dispense {volume}µL of liquid into a well that can currently only hold"
|
|
2190
|
+
f" {remaining_volume}µL (well {well_name} in labware {self._labware.get_display_name(labware_id)})"
|
|
2300
2191
|
)
|
|
2301
2192
|
else:
|
|
2302
2193
|
# TODO(pbm, 10-08-24): factor in well (LabwareStore) state volume
|
|
2303
2194
|
if volume > well_volumetric_capacity:
|
|
2304
2195
|
raise errors.InvalidDispenseVolumeError(
|
|
2305
|
-
f"Attempting to dispense {volume}µL of liquid into a well that can only hold
|
|
2196
|
+
f"Attempting to dispense {volume}µL of liquid into a well that can only hold"
|
|
2197
|
+
f" {well_volumetric_capacity}µL (well {well_name} in"
|
|
2198
|
+
f" labware {self._labware.get_display_name(labware_id)})"
|
|
2306
2199
|
)
|
|
2307
2200
|
|
|
2308
2201
|
def get_wells_covered_by_pipette_with_active_well(
|
|
@@ -2423,6 +2316,10 @@ class GeometryView:
|
|
|
2423
2316
|
raise errors.LocationNotAccessibleByPipetteError(
|
|
2424
2317
|
f"Cannot move pipette to {labware.loadName}, labware is off-deck."
|
|
2425
2318
|
)
|
|
2319
|
+
elif labware_location == WASTE_CHUTE_LOCATION:
|
|
2320
|
+
raise errors.LocationNotAccessibleByPipetteError(
|
|
2321
|
+
f"Cannot move pipette to {labware.loadName}, labware is in waste chute."
|
|
2322
|
+
)
|
|
2426
2323
|
elif isinstance(labware_location, ModuleLocation):
|
|
2427
2324
|
module = self._modules.get(labware_location.moduleId)
|
|
2428
2325
|
if ModuleModel.is_flex_stacker(module.model):
|