Skip to content

Commit 8203e3a

Browse files
refactor(api,shared-data): Clarify gripper offsets (#16711)
1 parent 78f6791 commit 8203e3a

File tree

10 files changed

+113
-47
lines changed

10 files changed

+113
-47
lines changed

api/src/opentrons/protocol_engine/commands/absorbance_reader/close_lid.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ async def execute(self, params: CloseLidParams) -> SuccessData[CloseLidResult]:
107107
)
108108
)
109109

110-
lid_gripper_offsets = self._state_view.labware.get_labware_gripper_offsets(
110+
# The lid's labware definition stores gripper offsets for itself in the
111+
# space normally meant for offsets for labware stacked atop it.
112+
lid_gripper_offsets = self._state_view.labware.get_child_gripper_offsets(
111113
loaded_lid.id, None
112114
)
113115
if lid_gripper_offsets is None:

api/src/opentrons/protocol_engine/commands/absorbance_reader/open_lid.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult]:
106106
mod_substate.module_id
107107
)
108108

109-
lid_gripper_offsets = self._state_view.labware.get_labware_gripper_offsets(
109+
# The lid's labware definition stores gripper offsets for itself in the
110+
# space normally meant for offsets for labware stacked atop it.
111+
lid_gripper_offsets = self._state_view.labware.get_child_gripper_offsets(
110112
loaded_lid.id, None
111113
)
112114
if lid_gripper_offsets is None:

api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44
from pydantic import BaseModel, Field
5-
from typing import TYPE_CHECKING, Optional, Type, cast
5+
from typing import TYPE_CHECKING, Optional, Type
66
from typing_extensions import Literal
77

88
from opentrons.hardware_control.types import Axis, OT3Mount
@@ -84,13 +84,25 @@ async def execute(
8484
"Cannot place labware when gripper is not gripping."
8585
)
8686

87-
# Allow propagation of LabwareNotLoadedError.
8887
labware_id = params.labwareId
88+
# Allow propagation of LabwareNotLoadedError.
8989
definition_uri = self._state_view.labware.get(labware_id).definitionUri
90-
final_offsets = self._state_view.labware.get_labware_gripper_offsets(
90+
91+
# todo(mm, 2024-11-06): This is only correct in the special case of an
92+
# absorbance reader lid. Its definition currently puts the offsets for *itself*
93+
# in the property that's normally meant for offsets for its *children.*
94+
final_offsets = self._state_view.labware.get_child_gripper_offsets(
9195
labware_id, None
9296
)
93-
drop_offset = cast(Point, final_offsets.dropOffset) if final_offsets else None
97+
drop_offset = (
98+
Point(
99+
final_offsets.dropOffset.x,
100+
final_offsets.dropOffset.y,
101+
final_offsets.dropOffset.z,
102+
)
103+
if final_offsets
104+
else None
105+
)
94106

95107
if isinstance(params.location, DeckSlotLocation):
96108
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(

api/src/opentrons/protocol_engine/state/geometry.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,7 @@ def get_total_nominal_gripper_offset_for_move_type(
12031203
extra_offset = LabwareOffsetVector(x=0, y=0, z=0)
12041204
if (
12051205
isinstance(ancestor, ModuleLocation)
1206+
# todo(mm, 2024-11-06): Do not access private module state; only use public ModuleView methods.
12061207
and self._modules._state.requested_model_by_id[ancestor.moduleId]
12071208
== ModuleModel.THERMOCYCLER_MODULE_V2
12081209
and labware_validation.validate_definition_is_lid(current_labware)
@@ -1241,6 +1242,19 @@ def get_total_nominal_gripper_offset_for_move_type(
12411242
+ extra_offset
12421243
)
12431244

1245+
# todo(mm, 2024-11-05): This may be incorrect because it does not take the following
1246+
# offsets into account:
1247+
#
1248+
# * The pickup offset in the definition of the parent of the gripped labware.
1249+
# * The "additional offset" or "user offset", e.g. the `pickUpOffset` and `dropOffset`
1250+
# params in the `moveLabware` command.
1251+
#
1252+
# For robustness, we should combine this with `get_gripper_labware_movement_waypoints()`.
1253+
#
1254+
# We should also be more explicit about which offsets act to move the gripper paddles
1255+
# relative to the gripped labware, and which offsets act to change how the gripped
1256+
# labware sits atop its parent. Those have different effects on how far the gripped
1257+
# labware juts beyond the paddles while it's in transit.
12441258
def check_gripper_labware_tip_collision(
12451259
self,
12461260
gripper_homed_position_z: float,
@@ -1321,11 +1335,11 @@ def _labware_gripper_offsets(
13211335
module_loc = self._modules.get_location(parent_location.moduleId)
13221336
slot_name = module_loc.slotName
13231337

1324-
slot_based_offset = self._labware.get_labware_gripper_offsets(
1338+
slot_based_offset = self._labware.get_child_gripper_offsets(
13251339
labware_id=labware_id, slot_name=slot_name.to_ot3_equivalent()
13261340
)
13271341

1328-
return slot_based_offset or self._labware.get_labware_gripper_offsets(
1342+
return slot_based_offset or self._labware.get_child_gripper_offsets(
13291343
labware_id=labware_id, slot_name=None
13301344
)
13311345

api/src/opentrons/protocol_engine/state/labware.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -928,19 +928,24 @@ def get_deck_default_gripper_offsets(self) -> Optional[LabwareMovementOffsetData
928928
else None
929929
)
930930

931-
def get_labware_gripper_offsets(
931+
def get_child_gripper_offsets(
932932
self,
933933
labware_id: str,
934934
slot_name: Optional[DeckSlotName],
935935
) -> Optional[LabwareMovementOffsetData]:
936-
"""Get the labware's gripper offsets of the specified type.
936+
"""Get the offsets that a labware says should be applied to children stacked atop it.
937+
938+
Params:
939+
labware_id: The ID of a parent labware (atop which another labware, the child, will be stacked).
940+
slot_name: The ancestor slot that the parent labware is ultimately loaded into,
941+
perhaps after going through a module in the middle.
937942
938943
Returns:
939-
If `slot_name` is provided, returns the gripper offsets that the labware definition
944+
If `slot_name` is provided, returns the gripper offsets that the parent labware definition
940945
specifies just for that slot, or `None` if the labware definition doesn't have an
941946
exact match.
942947
943-
If `slot_name` is `None`, returns the gripper offsets that the labware
948+
If `slot_name` is `None`, returns the gripper offsets that the parent labware
944949
definition designates as "default," or `None` if it doesn't designate any as such.
945950
"""
946951
parsed_offsets = self.get_definition(labware_id).gripperOffsets

api/tests/opentrons/protocol_engine/state/test_geometry_view.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2750,7 +2750,7 @@ def test_get_stacked_labware_total_nominal_offset_slot_specific(
27502750
DeckSlotLocation(slotName=DeckSlotName.SLOT_C1)
27512751
)
27522752
decoy.when(
2753-
mock_labware_view.get_labware_gripper_offsets(
2753+
mock_labware_view.get_child_gripper_offsets(
27542754
labware_id="adapter-id", slot_name=DeckSlotName.SLOT_C1
27552755
)
27562756
).then_return(
@@ -2802,12 +2802,12 @@ def test_get_stacked_labware_total_nominal_offset_default(
28022802
DeckSlotLocation(slotName=DeckSlotName.SLOT_4)
28032803
)
28042804
decoy.when(
2805-
mock_labware_view.get_labware_gripper_offsets(
2805+
mock_labware_view.get_child_gripper_offsets(
28062806
labware_id="adapter-id", slot_name=DeckSlotName.SLOT_C1
28072807
)
28082808
).then_return(None)
28092809
decoy.when(
2810-
mock_labware_view.get_labware_gripper_offsets(
2810+
mock_labware_view.get_child_gripper_offsets(
28112811
labware_id="adapter-id", slot_name=None
28122812
)
28132813
).then_return(

api/tests/opentrons/protocol_engine/state/test_labware_view.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,10 +1530,9 @@ def test_get_labware_gripper_offsets(
15301530
)
15311531

15321532
assert (
1533-
subject.get_labware_gripper_offsets(labware_id="plate-id", slot_name=None)
1534-
is None
1533+
subject.get_child_gripper_offsets(labware_id="plate-id", slot_name=None) is None
15351534
)
1536-
assert subject.get_labware_gripper_offsets(
1535+
assert subject.get_child_gripper_offsets(
15371536
labware_id="adapter-plate-id", slot_name=DeckSlotName.SLOT_D1
15381537
) == LabwareMovementOffsetData(
15391538
pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0),
@@ -1570,13 +1569,13 @@ def test_get_labware_gripper_offsets_default_no_slots(
15701569
)
15711570

15721571
assert (
1573-
subject.get_labware_gripper_offsets(
1572+
subject.get_child_gripper_offsets(
15741573
labware_id="labware-id", slot_name=DeckSlotName.SLOT_D1
15751574
)
15761575
is None
15771576
)
15781577

1579-
assert subject.get_labware_gripper_offsets(
1578+
assert subject.get_child_gripper_offsets(
15801579
labware_id="labware-id", slot_name=None
15811580
) == LabwareMovementOffsetData(
15821581
pickUpOffset=LabwareOffsetVector(x=1, y=2, z=3),

shared-data/labware/schemas/2.json

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@
6565
"type": "number"
6666
}
6767
}
68+
},
69+
"pickUpAndDropOffsets": {
70+
"type": "object",
71+
"required": ["pickUpOffset", "dropOffset"],
72+
"properties": {
73+
"pickUpOffset": {
74+
"$ref": "#/definitions/coordinates",
75+
"description": "Offset added to calculate pick-up coordinates."
76+
},
77+
"dropOffset": {
78+
"$ref": "#/definitions/coordinates",
79+
"description": "Offset added to calculate drop coordinates."
80+
}
81+
}
6882
}
6983
},
7084
"type": "object",
@@ -343,21 +357,23 @@
343357
},
344358
"gripperOffsets": {
345359
"type": "object",
346-
"description": "Offsets to be added when calculating the coordinates a gripper should go to when picking up or dropping a labware on this labware.",
360+
"description": "Offsets to add when picking up or dropping another labware stacked atop this one. Do not use this to adjust the position of the gripper paddles relative to this labware or the child labware; use `gripHeightFromLabwareBottom` on this definition or the child's definition for that.",
361+
"additionalProperties": {
362+
"$ref": "#/definitions/pickUpAndDropOffsets",
363+
"description": "Properties here are named for, and matched based on, the deck slot that this labware is atop--or, if this labware is atop a module, the deck slot that that module is atop."
364+
},
347365
"properties": {
348366
"default": {
349-
"type": "object",
350-
"properties": {
351-
"pickUpOffset": {
352-
"$ref": "#/definitions/coordinates",
353-
"description": "Offset added to calculate pick-up coordinates of a labware placed on this labware."
354-
},
355-
"dropOffset": {
356-
"$ref": "#/definitions/coordinates",
357-
"description": "Offset added to calculate drop coordinates of a labware placed on this labware."
358-
}
359-
},
360-
"required": ["pickUpOffset", "dropOffset"]
367+
"$ref": "#/definitions/pickUpAndDropOffsets",
368+
"description": "The offsets to use if there's no slot-specific match in `additionalProperties`."
369+
},
370+
"lidOffsets": {
371+
"$ref": "#/definitions/pickUpAndDropOffsets",
372+
"description": "Additional offsets for gripping this labware, if this labware is a lid. Beware this property's placement: instead of affecting the labware stacked atop this labware, like the rest of the `gripperOffsets` properties, it affects this labware."
373+
},
374+
"lidDisposalOffsets": {
375+
"$ref": "#/definitions/pickUpAndDropOffsets",
376+
"description": "Additional offsets for gripping this labware, if this labware is a lid and it's being moved to a trash bin. Beware this property's placement: instead of affecting the labware stacked atop this labware, like the rest of the `gripperOffsets` properties, it affects this labware."
361377
}
362378
}
363379
},

shared-data/labware/schemas/3.json

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@
6565
}
6666
}
6767
},
68+
"pickUpAndDropOffsets": {
69+
"type": "object",
70+
"required": ["pickUpOffset", "dropOffset"],
71+
"properties": {
72+
"pickUpOffset": {
73+
"$ref": "#/definitions/coordinates",
74+
"description": "Offset added to calculate pick-up coordinates."
75+
},
76+
"dropOffset": {
77+
"$ref": "#/definitions/coordinates",
78+
"description": "Offset added to calculate drop coordinates."
79+
}
80+
}
81+
},
6882
"SphericalSegment": {
6983
"type": "object",
7084
"description": "A partial sphere shaped section at the bottom of the well.",
@@ -538,21 +552,23 @@
538552
},
539553
"gripperOffsets": {
540554
"type": "object",
541-
"description": "Offsets to be added when calculating the coordinates a gripper should go to when picking up or dropping a labware on this labware.",
555+
"description": "Offsets to add when picking up or dropping another labware stacked atop this one. Do not use this to adjust the position of the gripper paddles relative to this labware or the child labware; use `gripHeightFromLabwareBottom` on this definition or the child's definition for that.",
556+
"additionalProperties": {
557+
"$ref": "#/definitions/pickUpAndDropOffsets",
558+
"description": "Properties here are named for, and matched based on, the deck slot that this labware is atop--or, if this labware is atop a module, the deck slot that that module is atop."
559+
},
542560
"properties": {
543561
"default": {
544-
"type": "object",
545-
"properties": {
546-
"pickUpOffset": {
547-
"$ref": "#/definitions/coordinates",
548-
"description": "Offset added to calculate pick-up coordinates of a labware placed on this labware."
549-
},
550-
"dropOffset": {
551-
"$ref": "#/definitions/coordinates",
552-
"description": "Offset added to calculate drop coordinates of a labware placed on this labware."
553-
}
554-
},
555-
"required": ["pickUpOffset", "dropOffset"]
562+
"$ref": "#/definitions/pickUpAndDropOffsets",
563+
"description": "The offsets to use if there's no slot-specific match in `additionalProperties`."
564+
},
565+
"lidOffsets": {
566+
"$ref": "#/definitions/pickUpAndDropOffsets",
567+
"description": "Additional offsets for gripping this labware, if this labware is a lid. Beware this property's placement: instead of affecting the labware stacked atop this labware, like the rest of the `gripperOffsets` properties, it affects this labware."
568+
},
569+
"lidDisposalOffsets": {
570+
"$ref": "#/definitions/pickUpAndDropOffsets",
571+
"description": "Additional offsets for gripping this labware, if this labware is a lid and it's being moved to a trash bin. Beware this property's placement: instead of affecting the labware stacked atop this labware, like the rest of the `gripperOffsets` properties, it affects this labware."
556572
}
557573
}
558574
},

shared-data/module/schemas/3.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@
141141
},
142142
"gripperOffsets": {
143143
"type": "object",
144-
"description": "Offsets to be added when calculating the coordinates a gripper should go to when picking up or dropping a labware on a module.",
144+
"description": "Offsets to be added when calculating the coordinates a gripper should go to when picking up or dropping a labware on this module.",
145145
"properties": {
146146
"default": {
147147
"type": "object",

0 commit comments

Comments
 (0)