Skip to content

Commit 29b7344

Browse files
authored
Fix Robotiq2F140 finger_length and make clearance configurable (#42)
Two changes: 1. Robotiq2F140.FINGER_LENGTH: 55mm → 114mm finger_length is the distance from the MuJoCo grasp_site to the fingertip along the approach axis. The previous value (55mm) was the pad length from the Robotiq datasheet, but the grasp_site sits at the base_mount origin — 100mm behind the pad base. Measured from the actual MuJoCo model: the pad tip corner is 114mm from the grasp_site. With 55mm, the TSR placed the gripper 55mm above the top face for the shallowest grasp. The real fingertips were actually at 114mm, so the gripper housing was 59mm lower than intended — colliding with the table for any object shorter than ~60mm. Sugar box (48mm) and gelatin box (28mm) were unpickable. With 114mm, the shallowest grasp correctly places the fingertips at the object's top edge, keeping the housing clear of the table. 2. ParallelJawGripper.clearance_fraction: hardcoded 0.3 → configurable The clearance (safety margin at grasp depth limits) was hardcoded as 30% of graspable_depth. Now a constructor parameter defaulting to 0.1 (10%). The old 30% was too conservative with the corrected finger_length — it rejected valid grasps on objects shorter than ~70mm.
1 parent 94ad1f1 commit 29b7344

6 files changed

Lines changed: 29 additions & 20 deletions

File tree

src/tsr/hands/parallel_jaw.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,28 @@ class ParallelJawGripper(GripperBase):
3737
max_aperture: Maximum jaw opening [m].
3838
"""
3939

40-
def __init__(self, finger_length: float, max_aperture: float):
40+
def __init__(
41+
self,
42+
finger_length: float,
43+
max_aperture: float,
44+
clearance_fraction: float = 0.1,
45+
):
4146
self.finger_length = finger_length
4247
self.max_aperture = max_aperture
48+
self.clearance_fraction = clearance_fraction
4349

4450
def _default_clearance(self, graspable_depth: float) -> float:
4551
"""Compute default clearance from graspable depth.
4652
47-
Clearance is 30% of the graspable depth — the distance fingers
48-
can wrap around the object. This ensures the shallowest grasp
49-
has enough contact area for friction to hold.
53+
Clearance is ``clearance_fraction`` of the graspable depth — the
54+
distance fingers can wrap around the object.
5055
5156
Args:
5257
graspable_depth: max penetration depth for this grasp geometry,
5358
typically min(finger_length, object_radius) for side grasps
5459
or finger_length for top/bottom grasps.
5560
"""
56-
return 0.3 * graspable_depth
61+
return self.clearance_fraction * graspable_depth
5762

5863
def _validate(self, cylinder_radius: float, preshape: float) -> None:
5964
if cylinder_radius <= 0:
@@ -941,7 +946,11 @@ def renderer(self):
941946
class Robotiq2F140(ParallelJawGripper):
942947
"""Robotiq 2F-140 parallel gripper.
943948
944-
Fixed hardware parameters: finger_length=55 mm, max_aperture=140 mm.
949+
Fixed hardware parameters: finger_length=82 mm, max_aperture=140 mm.
950+
951+
finger_length is the distance from the MuJoCo grasp_site to the finger
952+
pad tip (measured along the approach axis). The grasp_site is at the
953+
base_mount origin; the pad tip is 114 mm along the approach direction.
945954
946955
Outputs poses in the canonical TSR EE frame (z=approach, y=finger-opening,
947956
x=palm normal). The corresponding MuJoCo model (geodude_assets 2f140.xml)
@@ -950,7 +959,7 @@ class Robotiq2F140(ParallelJawGripper):
950959
directly.
951960
"""
952961

953-
FINGER_LENGTH = 0.055
962+
FINGER_LENGTH = 0.114
954963
MAX_APERTURE = 0.140
955964

956965
def __init__(self):

tests/tsr/hands/test_box_grasp.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def test_palm_offset_is_h_palm_outside_face(self):
6363

6464
def test_preshape_matches_span_dim(self):
6565
"""Auto-computed preshape = span_dim + clearance for each orientation."""
66-
clearance = 0.3 * self.gripper.finger_length
66+
clearance = self.gripper.clearance_fraction * self.gripper.finger_length
6767
for t in self.gripper.grasp_box(SX, SY, SZ):
6868
ps = t.preshape[0]
6969
# preshape should be close to one of the box face dimensions + clearance
@@ -121,7 +121,7 @@ def test_names_contain_span_labels(self):
121121
self.assertTrue(any("span-y" in n for n in names))
122122

123123
def test_span_x_slides_in_y(self):
124-
clearance = 0.3 * self.gripper.finger_length
124+
clearance = self.gripper.clearance_fraction * self.gripper.finger_length
125125
span_x_templates = [t for t in self.gripper.grasp_box_top(SX, SY, SZ)
126126
if "span-x" in t.name]
127127
for t in span_x_templates:
@@ -130,7 +130,7 @@ def test_span_x_slides_in_y(self):
130130
self.assertAlmostEqual(t.Bw[1, 1], (SY / 2 - clearance))
131131

132132
def test_span_y_slides_in_x(self):
133-
clearance = 0.3 * self.gripper.finger_length
133+
clearance = self.gripper.clearance_fraction * self.gripper.finger_length
134134
span_y_templates = [t for t in self.gripper.grasp_box_top(SX, SY, SZ)
135135
if "span-y" in t.name]
136136
for t in span_y_templates:
@@ -212,7 +212,7 @@ def test_tw_e_valid_se3(self):
212212
_check_se3(self, t.Tw_e[:3, :3])
213213

214214
def test_span_y_slides_in_z_only(self):
215-
clearance = 0.3 * self.gripper.finger_length
215+
clearance = self.gripper.clearance_fraction * self.gripper.finger_length
216216
span_y = [t for t in self.gripper.grasp_box_face_x(SX, SY, SZ)
217217
if "span-y" in t.name]
218218
for t in span_y:
@@ -222,7 +222,7 @@ def test_span_y_slides_in_z_only(self):
222222
self.assertAlmostEqual(t.Bw[2, 1], (SZ / 2 - clearance))
223223

224224
def test_span_z_slides_in_y_only(self):
225-
clearance = 0.3 * self.gripper.finger_length
225+
clearance = self.gripper.clearance_fraction * self.gripper.finger_length
226226
span_z = [t for t in self.gripper.grasp_box_face_x(SX, SY, SZ)
227227
if "span-z" in t.name]
228228
for t in span_z:
@@ -270,7 +270,7 @@ def test_tw_e_valid_se3(self):
270270
_check_se3(self, t.Tw_e[:3, :3])
271271

272272
def test_span_x_slides_in_z_only(self):
273-
clearance = 0.3 * self.gripper.finger_length
273+
clearance = self.gripper.clearance_fraction * self.gripper.finger_length
274274
span_x = [t for t in self.gripper.grasp_box_face_y(SX, SY, SZ)
275275
if "span-x" in t.name]
276276
for t in span_x:

tests/tsr/hands/test_parallel_jaw.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def test_narrow_height_raises(self):
4242
self.gripper.grasp_cylinder_side(R, 0.001)
4343

4444
def test_default_preshape_is_2r_plus_clearance(self):
45-
clearance = 0.3 * min(self.gripper.finger_length, R)
45+
clearance = self.gripper.clearance_fraction * min(self.gripper.finger_length, R)
4646
expected = 2 * R + clearance
4747
templates = self.gripper.grasp_cylinder_side(R, H)
4848
np.testing.assert_allclose(templates[0].preshape[0], expected)

tests/tsr/hands/test_robotiq.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def setUp(self):
1111
self.gripper = Robotiq2F140()
1212

1313
def test_fixed_params(self):
14-
self.assertAlmostEqual(self.gripper.finger_length, 0.055)
14+
self.assertAlmostEqual(self.gripper.finger_length, 0.114)
1515
self.assertAlmostEqual(self.gripper.max_aperture, 0.140)
1616

1717
def test_is_subclass_of_parallel_jaw(self):
@@ -21,7 +21,7 @@ def test_outputs_canonical_frames(self):
2121
# Robotiq2F140 now outputs canonical TSR EE poses (no frame correction).
2222
# It should produce identical Tw_e as a plain ParallelJawGripper with
2323
# the same hardware parameters.
24-
base = ParallelJawGripper(finger_length=0.055, max_aperture=0.140)
24+
base = ParallelJawGripper(finger_length=0.114, max_aperture=0.140)
2525
t_base = base.grasp_cylinder_side(0.040, 0.10)
2626
t_robotiq = self.gripper.grasp_cylinder_side(0.040, 0.10)
2727

tests/tsr/hands/test_sphere_grasp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def test_z_ee_points_inward(self):
4747
err_msg=f"z_EE not inward in {t.name}")
4848

4949
def test_standoff_within_expected_range(self):
50-
clearance = 0.3 * min(FL, RADIUS)
50+
clearance = self.gripper.clearance_fraction * min(FL, RADIUS)
5151
depth_min = RADIUS # fingertips at center
5252
depth_max = min(FL, 2 * RADIUS) - clearance
5353
ro_max = RADIUS + FL - depth_min # = FL
@@ -84,7 +84,7 @@ def test_bw_full_yaw_freedom(self):
8484
# ── Preshape ──────────────────────────────────────────────────────────
8585

8686
def test_default_preshape_is_diameter_plus_clearance(self):
87-
clearance = 0.3 * min(FL, RADIUS)
87+
clearance = self.gripper.clearance_fraction * min(FL, RADIUS)
8888
expected = 2 * RADIUS + clearance
8989
for t in self.templates:
9090
self.assertAlmostEqual(t.preshape[0], expected)

tests/tsr/hands/test_torus_grasp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def test_bw_no_roll_pitch_freedom(self):
116116
# ── Preshape ──────────────────────────────────────────────────────────
117117

118118
def test_default_preshape_is_tube_diameter_plus_clearance(self):
119-
clearance = 0.3 * min(FL, Sr)
119+
clearance = self.gripper.clearance_fraction * min(FL, Sr)
120120
expected = 2 * Sr + clearance
121121
for t in self.templates:
122122
self.assertAlmostEqual(t.preshape[0], expected)
@@ -229,7 +229,7 @@ def test_bw_no_roll_pitch_freedom(self):
229229
self.assertEqual(t.Bw[4, 0], t.Bw[4, 1])
230230

231231
def test_preshape_spans_outer_diameter(self):
232-
clearance = 0.3 * FL
232+
clearance = self.gripper.clearance_fraction * FL
233233
expected = 2 * (SR + Sr) + clearance
234234
for t in self.gripper.grasp_torus_span(SR, Sr):
235235
self.assertAlmostEqual(t.preshape[0], expected)

0 commit comments

Comments
 (0)