Skip to content

Commit a5303d1

Browse files
authored
feat(engine): use grip force and height from definitions in pe (#13233)
* fetch grip height and force from definitions and use for labware movement in PE * updated test * updated force values, allow force and height of zero * updated tests with changed force values
1 parent 5cfb86d commit a5303d1

File tree

18 files changed

+137
-51
lines changed

18 files changed

+137
-51
lines changed

api/src/opentrons/protocol_engine/execution/labware_movement.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
from __future__ import annotations
33

44
from typing import Optional, TYPE_CHECKING
5-
from opentrons_shared_data.gripper.constants import (
6-
LABWARE_GRIP_FORCE,
7-
IDLE_STATE_GRIP_FORCE,
8-
)
5+
from opentrons_shared_data.gripper.constants import IDLE_STATE_GRIP_FORCE
96

107
from opentrons.hardware_control import HardwareControlAPI
118
from opentrons.hardware_control.types import OT3Mount, Axis
@@ -117,10 +114,10 @@ async def move_labware_with_gripper(
117114
additional_offset_vector=user_offset_data,
118115
)
119116
)
120-
from_labware_center = self._state_store.geometry.get_labware_center(
117+
from_labware_center = self._state_store.geometry.get_labware_grip_point(
121118
labware_id=labware_id, location=current_location
122119
)
123-
to_labware_center = self._state_store.geometry.get_labware_center(
120+
to_labware_center = self._state_store.geometry.get_labware_grip_point(
124121
labware_id=labware_id, location=new_location
125122
)
126123
movement_waypoints = get_gripper_labware_movement_waypoints(
@@ -129,12 +126,13 @@ async def move_labware_with_gripper(
129126
gripper_home_z=gripper_homed_position.z,
130127
offset_data=final_offsets,
131128
)
129+
labware_grip_force = self._state_store.labware.get_grip_force(labware_id)
132130

133131
for waypoint_data in movement_waypoints:
134132
if waypoint_data.jaw_open:
135133
await ot3api.ungrip()
136134
else:
137-
await ot3api.grip(force_newtons=LABWARE_GRIP_FORCE)
135+
await ot3api.grip(force_newtons=labware_grip_force)
138136
await ot3api.move_to(
139137
mount=gripper_mount, abs_position=waypoint_data.position
140138
)

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

+13-6
Original file line numberDiff line numberDiff line change
@@ -416,17 +416,24 @@ def ensure_location_not_occupied(
416416
self._modules.raise_if_module_in_location(location)
417417
return location
418418

419-
def get_labware_center(
419+
def get_labware_grip_point(
420420
self,
421421
labware_id: str,
422422
location: Union[DeckSlotLocation, ModuleLocation, OnLabwareLocation],
423423
) -> Point:
424-
"""Get the center point of the labware as placed on the given location.
424+
"""Get the grip point of the labware as placed on the given location.
425425
426-
Returns the absolute position of the labware as if it were placed on the
427-
specified location. Labware offset not included.
426+
Returns the absolute position of the labware's gripping point as if
427+
it were placed on the specified location. Labware offset (LPC offset) not included.
428+
429+
Grip point is the location where critical point of the gripper should move to
430+
in order to pick/drop the given labware in the specified location.
431+
It is calculated as the xy center of the slot with z as the point indicated by
432+
z-position of labware bottom + grip height from labware bottom.
428433
"""
429-
labware_dimensions = self._labware.get_dimensions(labware_id)
434+
grip_height_from_labware_bottom = (
435+
self._labware.get_grip_height_from_labware_bottom(labware_id)
436+
)
430437
offset = LabwareOffsetVector(x=0, y=0, z=0)
431438
location_slot: DeckSlotName
432439

@@ -452,7 +459,7 @@ def get_labware_center(
452459
return Point(
453460
slot_center.x + offset.x,
454461
slot_center.y + offset.y,
455-
slot_center.z + offset.z + labware_dimensions.z / 2,
462+
slot_center.z + offset.z + grip_height_from_labware_bottom,
456463
)
457464

458465
def get_extra_waypoints(

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

+17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
)
1818

1919
from opentrons_shared_data.deck.dev_types import DeckDefinitionV3, SlotDefV3
20+
from opentrons_shared_data.gripper.constants import LABWARE_GRIP_FORCE
2021
from opentrons_shared_data.labware.labware_definition import LabwareRole
2122
from opentrons_shared_data.pipette.dev_types import LabwareUri
2223

@@ -763,3 +764,19 @@ def get_labware_gripper_offsets(
763764
if parsed_offsets
764765
else None
765766
)
767+
768+
def get_grip_force(self, labware_id: str) -> float:
769+
"""Get the recommended grip force for gripping labware using gripper."""
770+
recommended_force = self.get_definition(labware_id).gripForce
771+
return (
772+
recommended_force if recommended_force is not None else LABWARE_GRIP_FORCE
773+
)
774+
775+
def get_grip_height_from_labware_bottom(self, labware_id: str) -> float:
776+
"""Get the recommended grip height from labware bottom, if present."""
777+
recommended_height = self.get_definition(labware_id).gripHeightFromLabwareBottom
778+
return (
779+
recommended_height
780+
if recommended_height is not None
781+
else self.get_dimensions(labware_id).z / 2
782+
)

api/tests/opentrons/protocol_engine/conftest.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,15 @@ def ot3_fixed_trash_def() -> LabwareDefinition:
9696
def well_plate_def() -> LabwareDefinition:
9797
"""Get the definition of a 96 well plate."""
9898
return LabwareDefinition.parse_obj(
99-
load_definition("corning_96_wellplate_360ul_flat", 1)
99+
load_definition("corning_96_wellplate_360ul_flat", 2)
100+
)
101+
102+
103+
@pytest.fixture(scope="session")
104+
def flex_50uL_tiprack() -> LabwareDefinition:
105+
"""Get the definition of a Flex 50uL tiprack."""
106+
return LabwareDefinition.parse_obj(
107+
load_definition("opentrons_flex_96_filtertiprack_50ul", 1)
100108
)
101109

102110

api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
from typing import TYPE_CHECKING, Union
99

1010
from opentrons.protocol_engine.execution import EquipmentHandler, MovementHandler
11-
from opentrons_shared_data.gripper.constants import (
12-
LABWARE_GRIP_FORCE,
13-
IDLE_STATE_GRIP_FORCE,
14-
)
11+
from opentrons_shared_data.gripper.constants import IDLE_STATE_GRIP_FORCE
1512
from opentrons.hardware_control import HardwareControlAPI
1613
from opentrons.types import DeckSlotName, Point
1714

@@ -184,17 +181,19 @@ async def test_move_labware_with_gripper(
184181
).then_return(final_offset_data)
185182

186183
decoy.when(
187-
state_store.geometry.get_labware_center(
184+
state_store.geometry.get_labware_grip_point(
188185
labware_id="my-teleporting-labware", location=from_location
189186
)
190187
).then_return(Point(101, 102, 119.5))
191188

192189
decoy.when(
193-
state_store.geometry.get_labware_center(
190+
state_store.geometry.get_labware_grip_point(
194191
labware_id="my-teleporting-labware", location=to_location
195192
)
196193
).then_return(Point(201, 202, 219.5))
197-
194+
decoy.when(
195+
state_store.labware.get_grip_force("my-teleporting-labware")
196+
).then_return(100)
198197
mock_tc_context_manager = decoy.mock()
199198
decoy.when(
200199
thermocycler_plate_lifter.lift_plate_for_labware_movement(
@@ -234,23 +233,23 @@ async def test_move_labware_with_gripper(
234233
decoy.verify(
235234
await ot3_hardware_api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G]),
236235
await mock_tc_context_manager.__aenter__(),
237-
await ot3_hardware_api.grip(force_newtons=LABWARE_GRIP_FORCE),
236+
await ot3_hardware_api.grip(force_newtons=100),
238237
await ot3_hardware_api.move_to(
239238
mount=gripper, abs_position=expected_waypoints[0]
240239
),
241240
await ot3_hardware_api.ungrip(),
242241
await ot3_hardware_api.move_to(
243242
mount=gripper, abs_position=expected_waypoints[1]
244243
),
245-
await ot3_hardware_api.grip(force_newtons=LABWARE_GRIP_FORCE),
244+
await ot3_hardware_api.grip(force_newtons=100),
246245
await ot3_hardware_api.move_to(
247246
mount=gripper, abs_position=expected_waypoints[2]
248247
),
249-
await ot3_hardware_api.grip(force_newtons=LABWARE_GRIP_FORCE),
248+
await ot3_hardware_api.grip(force_newtons=100),
250249
await ot3_hardware_api.move_to(
251250
mount=gripper, abs_position=expected_waypoints[3]
252251
),
253-
await ot3_hardware_api.grip(force_newtons=LABWARE_GRIP_FORCE),
252+
await ot3_hardware_api.grip(force_newtons=100),
254253
await ot3_hardware_api.move_to(
255254
mount=gripper, abs_position=expected_waypoints[4]
256255
),

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

+15-15
Original file line numberDiff line numberDiff line change
@@ -1078,11 +1078,11 @@ def test_ensure_location_not_occupied_raises(
10781078
@pytest.mark.parametrize(
10791079
argnames=["location", "expected_center_point"],
10801080
argvalues=[
1081-
(DeckSlotLocation(slotName=DeckSlotName.SLOT_1), Point(101.0, 102.0, 119.5)),
1082-
(ModuleLocation(moduleId="module-id"), Point(111.0, 122.0, 149.5)),
1081+
(DeckSlotLocation(slotName=DeckSlotName.SLOT_1), Point(101.0, 102.0, 203)),
1082+
(ModuleLocation(moduleId="module-id"), Point(111.0, 122.0, 233)),
10831083
],
10841084
)
1085-
def test_get_labware_center(
1085+
def test_get_labware_grip_point(
10861086
decoy: Decoy,
10871087
labware_view: LabwareView,
10881088
module_view: ModuleView,
@@ -1091,10 +1091,10 @@ def test_get_labware_center(
10911091
location: Union[DeckSlotLocation, ModuleLocation],
10921092
expected_center_point: Point,
10931093
) -> None:
1094-
"""It should get the center point of the labware at the specified location."""
1095-
decoy.when(labware_view.get_dimensions(labware_id="labware-id")).then_return(
1096-
Dimensions(x=11, y=22, z=33)
1097-
)
1094+
"""It should get the grip point of the labware at the specified location."""
1095+
decoy.when(
1096+
labware_view.get_grip_height_from_labware_bottom("labware-id")
1097+
).then_return(100)
10981098

10991099
if isinstance(location, ModuleLocation):
11001100
decoy.when(labware_view.get_deck_definition()).then_return(
@@ -1113,21 +1113,21 @@ def test_get_labware_center(
11131113
decoy.when(labware_view.get_slot_center_position(DeckSlotName.SLOT_1)).then_return(
11141114
Point(x=101, y=102, z=103)
11151115
)
1116-
labware_center = subject.get_labware_center(
1116+
labware_center = subject.get_labware_grip_point(
11171117
labware_id="labware-id", location=location
11181118
)
11191119

11201120
assert labware_center == expected_center_point
11211121

11221122

1123-
def test_get_labware_center_on_labware(
1123+
def test_get_labware_grip_point_on_labware(
11241124
decoy: Decoy,
11251125
labware_view: LabwareView,
11261126
module_view: ModuleView,
11271127
ot2_standard_deck_def: DeckDefinitionV3,
11281128
subject: GeometryView,
11291129
) -> None:
1130-
"""It should get the center point of a labware on another labware."""
1130+
"""It should get the grip point of a labware on another labware."""
11311131
decoy.when(labware_view.get(labware_id="labware-id")).then_return(
11321132
LoadedLabware(
11331133
id="labware-id",
@@ -1145,12 +1145,12 @@ def test_get_labware_center_on_labware(
11451145
)
11461146
)
11471147

1148-
decoy.when(labware_view.get_dimensions("labware-id")).then_return(
1149-
Dimensions(x=500, y=5001, z=10)
1150-
)
11511148
decoy.when(labware_view.get_dimensions("below-id")).then_return(
11521149
Dimensions(x=1000, y=1001, z=11)
11531150
)
1151+
decoy.when(
1152+
labware_view.get_grip_height_from_labware_bottom("labware-id")
1153+
).then_return(100)
11541154
decoy.when(
11551155
labware_view.get_labware_overlap_offsets("labware-id", "below-name")
11561156
).then_return(OverlapOffset(x=0, y=1, z=6))
@@ -1159,11 +1159,11 @@ def test_get_labware_center_on_labware(
11591159
Point(x=5, y=9, z=10)
11601160
)
11611161

1162-
labware_center = subject.get_labware_center(
1162+
grip_point = subject.get_labware_grip_point(
11631163
labware_id="labware-id", location=OnLabwareLocation(labwareId="below-id")
11641164
)
11651165

1166-
assert labware_center == Point(5, 10, 20)
1166+
assert grip_point == Point(5, 10, 115.0)
11671167

11681168

11691169
@pytest.mark.parametrize(

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

+47
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@
4747
displayName="Fancy Plate Name",
4848
)
4949

50+
flex_tiprack = LoadedLabware(
51+
id="flex-tiprack-id",
52+
loadName="flex-tiprack-load-name",
53+
location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1),
54+
definitionUri="some-flex-tiprack-uri",
55+
offsetId=None,
56+
displayName="Flex Tiprack Name",
57+
)
58+
5059
reservoir = LoadedLabware(
5160
id="reservoir-id",
5261
loadName="reservoir-load-name",
@@ -1315,3 +1324,41 @@ def test_get_labware_gripper_offsets(
13151324
pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0),
13161325
dropOffset=LabwareOffsetVector(x=2, y=0, z=0),
13171326
)
1327+
1328+
1329+
def test_get_grip_force(
1330+
flex_50uL_tiprack: LabwareDefinition,
1331+
reservoir_def: LabwareDefinition,
1332+
) -> None:
1333+
"""It should get the grip force, if present, from labware definition or return default."""
1334+
subject = get_labware_view(
1335+
labware_by_id={"flex-tiprack-id": flex_tiprack, "reservoir-id": reservoir},
1336+
definitions_by_uri={
1337+
"some-flex-tiprack-uri": flex_50uL_tiprack,
1338+
"some-reservoir-uri": reservoir_def,
1339+
},
1340+
)
1341+
1342+
assert subject.get_grip_force("flex-tiprack-id") == 16 # from definition
1343+
assert subject.get_grip_force("reservoir-id") == 15 # default
1344+
1345+
1346+
def test_get_grip_height_from_labware_bottom(
1347+
well_plate_def: LabwareDefinition,
1348+
reservoir_def: LabwareDefinition,
1349+
) -> None:
1350+
"""It should get the grip height, if present, from labware definition or return default."""
1351+
subject = get_labware_view(
1352+
labware_by_id={"plate-id": plate, "reservoir-id": reservoir},
1353+
definitions_by_uri={
1354+
"some-plate-uri": well_plate_def,
1355+
"some-reservoir-uri": reservoir_def,
1356+
},
1357+
)
1358+
1359+
assert (
1360+
subject.get_grip_height_from_labware_bottom("plate-id") == 12.2
1361+
) # from definition
1362+
assert (
1363+
subject.get_grip_height_from_labware_bottom("reservoir-id") == 15.7
1364+
) # default

protocol-designer/fixtures/protocol/7/doItAllV7.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1390,7 +1390,7 @@
13901390
"yDimension": 85.48,
13911391
"zDimension": 15.7
13921392
},
1393-
"gripForce": 3,
1393+
"gripForce": 15,
13941394
"gripHeightFromLabwareBottom": 10.65,
13951395
"wells": {
13961396
"A1": {

shared-data/labware/definitions/2/armadillo_96_wellplate_200ul_pcr_full_skirt/2.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
"x": 0,
3737
"y": 0,
3838
"z": 10.95
39+
},
40+
"opentrons_96_well_aluminum_block": {
41+
"x": 0,
42+
"y": 0,
43+
"z": 11.91
3944
}
4045
},
4146
"stackingOffsetWithModule": {
@@ -45,7 +50,7 @@
4550
"z": 3.54
4651
}
4752
},
48-
"gripForce": 6,
53+
"gripForce": 15,
4954
"gripHeightFromLabwareBottom": 10,
5055
"ordering": [
5156
["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"],

shared-data/labware/definitions/2/biorad_384_wellplate_50ul/2.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@
460460
"yDimension": 85.48,
461461
"zDimension": 10.4
462462
},
463-
"gripForce": 6,
463+
"gripForce": 15,
464464
"gripHeightFromLabwareBottom": 9.3,
465465
"wells": {
466466
"A1": {

shared-data/labware/definitions/2/biorad_96_wellplate_200ul_pcr/2.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"isMagneticModuleCompatible": true,
3535
"magneticModuleEngageHeight": 18
3636
},
37-
"gripForce": 6,
37+
"gripForce": 15,
3838
"gripHeightFromLabwareBottom": 10.14,
3939
"wells": {
4040
"H1": {

shared-data/labware/definitions/2/corning_384_wellplate_112ul_flat/2.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@
453453
"isMagneticModuleCompatible": false,
454454
"loadName": "corning_384_wellplate_112ul_flat"
455455
},
456-
"gripForce": 10,
456+
"gripForce": 15,
457457
"gripHeightFromLabwareBottom": 12.4,
458458
"wells": {
459459
"P1": {

shared-data/labware/definitions/2/corning_96_wellplate_360ul_flat/2.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"y": 0,
6767
"z": 0
6868
},
69-
"gripForce": 10,
69+
"gripForce": 15,
7070
"gripHeightFromLabwareBottom": 12.2,
7171
"wells": {
7272
"H1": {

0 commit comments

Comments
 (0)