urkit 0.1.0__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.
- urkit/__init__.py +99 -0
- urkit/__main__.py +116 -0
- urkit/cli/__init__.py +6 -0
- urkit/cli/colors.py +99 -0
- urkit/cli/connection_monitor.py +154 -0
- urkit/cli/points.py +293 -0
- urkit/cli/teach.py +1181 -0
- urkit/config.py +78 -0
- urkit/connection.py +580 -0
- urkit/exceptions.py +51 -0
- urkit/geometry.py +413 -0
- urkit/gripper/__init__.py +36 -0
- urkit/gripper/base.py +116 -0
- urkit/gripper/digital.py +140 -0
- urkit/gripper/presets.py +113 -0
- urkit/gripper/robotiq.py +322 -0
- urkit/gripper/robotiq_preamble.py +1356 -0
- urkit/io.py +311 -0
- urkit/motion.py +504 -0
- urkit/points.py +277 -0
- urkit/robot.py +1510 -0
- urkit/telemetry.py +177 -0
- urkit-0.1.0.dist-info/METADATA +612 -0
- urkit-0.1.0.dist-info/RECORD +27 -0
- urkit-0.1.0.dist-info/WHEEL +5 -0
- urkit-0.1.0.dist-info/entry_points.txt +2 -0
- urkit-0.1.0.dist-info/top_level.txt +1 -0
urkit/gripper/presets.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Gripper presets and configuration dataclasses.
|
|
2
|
+
|
|
3
|
+
Built-in presets for common Robotiq grippers. Each preset carries the
|
|
4
|
+
mass, center of gravity, TCP offset, and backend type — everything
|
|
5
|
+
needed to configure the robot for that gripper in a single argument.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
from urkit import URRobot
|
|
10
|
+
from urkit.gripper.presets import ROBOTIQ_2F_85
|
|
11
|
+
|
|
12
|
+
# One arg — specs + backend
|
|
13
|
+
robot = URRobot(ip="192.168.1.100", gripper=ROBOTIQ_2F_85)
|
|
14
|
+
|
|
15
|
+
# Digital I/O gripper
|
|
16
|
+
from urkit.gripper.presets import DigitalGripperConfig
|
|
17
|
+
|
|
18
|
+
robot = URRobot(ip="192.168.1.100", gripper=DigitalGripperConfig(pin=3))
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class GripperPreset:
|
|
28
|
+
"""Gripper preset with physical specs and backend type.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
name: Human-readable identifier (e.g. ``"2F-85"``).
|
|
32
|
+
mass: Mass in kg.
|
|
33
|
+
center_of_gravity: Center of gravity in tool coordinates [x, y, z], meters.
|
|
34
|
+
tcp_offset: TCP offset [x, y, z, rx, ry, rz], meters/radians.
|
|
35
|
+
backend: Gripper backend name (e.g. ``"robotiq"``).
|
|
36
|
+
max_mm: Maximum finger travel in mm (for ``set_position()``).
|
|
37
|
+
force: Gripper force 0-100 (default 100).
|
|
38
|
+
speed: Gripper speed 0-100 (default 100).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
mass: float
|
|
43
|
+
center_of_gravity: list[float]
|
|
44
|
+
tcp_offset: list[float]
|
|
45
|
+
backend: str
|
|
46
|
+
max_mm: int = 50
|
|
47
|
+
force: int = 100
|
|
48
|
+
speed: int = 100
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class DigitalGripperConfig:
|
|
53
|
+
"""Configuration for a digital I/O gripper.
|
|
54
|
+
|
|
55
|
+
Digital grippers have no standard physical specs — just a pin
|
|
56
|
+
and polarity. Pass this instead of a ``GripperPreset`` when
|
|
57
|
+
using a suction cup, solenoid gripper, or custom actuator.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
pin: Digital output pin index (0-7).
|
|
61
|
+
close_on_high: If True, HIGH signal closes the gripper
|
|
62
|
+
(default True — suction cups, electromagnetic grippers).
|
|
63
|
+
If False, LOW signal closes it (normally-closed solenoid).
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
pin: int
|
|
67
|
+
close_on_high: bool = True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
# Robotiq 2-Finger Adaptive Grippers
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
ROBOTIQ_2F_85 = GripperPreset(
|
|
75
|
+
name="2F-85",
|
|
76
|
+
mass=0.921,
|
|
77
|
+
center_of_gravity=[0.0, 0.0, 0.060],
|
|
78
|
+
tcp_offset=[0.0, 0.0, 0.174, 0.0, 0.0, 0.0],
|
|
79
|
+
backend="robotiq",
|
|
80
|
+
max_mm=85,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
ROBOTIQ_2F_140 = GripperPreset(
|
|
84
|
+
name="2F-140",
|
|
85
|
+
mass=1.013,
|
|
86
|
+
center_of_gravity=[0.0, 0.0, 0.0755],
|
|
87
|
+
tcp_offset=[0.0, 0.0, 0.244, 0.0, 0.0, 0.0],
|
|
88
|
+
backend="robotiq",
|
|
89
|
+
max_mm=140,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Robotiq 2F-140-E (Hand-E series)
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
ROBOTIQ_HAND_E = GripperPreset(
|
|
97
|
+
name="Hand-E",
|
|
98
|
+
mass=1.068,
|
|
99
|
+
center_of_gravity=[0.0, 0.0, 0.059],
|
|
100
|
+
tcp_offset=[0.0, 0.0, 0.157, 0.0, 0.0, 0.0],
|
|
101
|
+
backend="robotiq",
|
|
102
|
+
max_mm=50,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Registry — all built-in presets
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
PRESETS: dict[str, GripperPreset] = {
|
|
110
|
+
p.name.upper(): p
|
|
111
|
+
for p in (ROBOTIQ_2F_85, ROBOTIQ_2F_140, ROBOTIQ_HAND_E)
|
|
112
|
+
}
|
|
113
|
+
"""Lookup dict keyed by uppercase preset name."""
|
urkit/gripper/robotiq.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""Robotiq 2F gripper backend via RTDE preamble-based scripting.
|
|
2
|
+
|
|
3
|
+
Controls the gripper by sending URScript commands through RTDE's
|
|
4
|
+
``sendCustomScriptFunction()`` method, using the official Robotiq
|
|
5
|
+
preamble (``rq_*`` function library) for all communication.
|
|
6
|
+
|
|
7
|
+
The preamble is prepended to every command so the robot has the
|
|
8
|
+
function definitions and a live socket context. This enables
|
|
9
|
+
synchronous motion: ``rq_move_and_wait`` polls the gripper's OBJ
|
|
10
|
+
register via ``socket_read_byte_list`` until the move completes or
|
|
11
|
+
contact is detected — all running on the robot side.
|
|
12
|
+
|
|
13
|
+
Each ``sendCustomScriptFunction`` call blocks until the full script
|
|
14
|
+
(preamble + function) finishes executing on the robot, making all
|
|
15
|
+
gripper operations synchronous by default.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
import threading
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
23
|
+
|
|
24
|
+
from urkit.exceptions import GripperError
|
|
25
|
+
from urkit.gripper.base import Gripper
|
|
26
|
+
from urkit.gripper.robotiq_preamble import ROBOTIQ_PREAMBLE
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from rtde_control import RTDEControlInterface
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RobotiqGripper(Gripper):
|
|
35
|
+
"""Robotiq 2F gripper via RTDE preamble-based scripting.
|
|
36
|
+
|
|
37
|
+
Uses the official Robotiq preamble (``rq_*`` URScript functions)
|
|
38
|
+
for all gripper communication. Every command prepends the preamble
|
|
39
|
+
so the robot has function definitions and a persistent socket
|
|
40
|
+
context, enabling synchronous motion with built-in polling.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
rtde_control: RTDEControlInterface (required).
|
|
44
|
+
max_mm: Maximum finger travel in mm (default 50).
|
|
45
|
+
force: Gripper force, 0-100 (default 100).
|
|
46
|
+
speed: Gripper speed, 0-100 (default 100).
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
GripperError: If rtde_control is not provided or force/speed
|
|
50
|
+
are out of range.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
*,
|
|
56
|
+
rtde_control: "RTDEControlInterface",
|
|
57
|
+
max_mm: int = 50,
|
|
58
|
+
force: int = 100,
|
|
59
|
+
speed: int = 100,
|
|
60
|
+
**kwargs, # rtde_receive, robot_ip passed by factory but not used
|
|
61
|
+
) -> None:
|
|
62
|
+
if rtde_control is None:
|
|
63
|
+
raise GripperError(
|
|
64
|
+
"RobotiqGripper requires rtde_control (RTDEControlInterface)."
|
|
65
|
+
)
|
|
66
|
+
if not 0 <= force <= 100:
|
|
67
|
+
raise GripperError(
|
|
68
|
+
f"Robotiq gripper force must be 0-100, got {force}."
|
|
69
|
+
)
|
|
70
|
+
if not 0 <= speed <= 100:
|
|
71
|
+
raise GripperError(
|
|
72
|
+
f"Robotiq gripper speed must be 0-100, got {speed}."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
self._rtde = rtde_control
|
|
76
|
+
self._max_mm = max_mm
|
|
77
|
+
self._force = force
|
|
78
|
+
self._speed = speed
|
|
79
|
+
self._activated = False
|
|
80
|
+
|
|
81
|
+
logger.info(
|
|
82
|
+
"RobotiqGripper initialized (preamble, max_mm=%d)", max_mm
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# ------------------------------------------------------------------
|
|
86
|
+
# Internal helpers
|
|
87
|
+
# ------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
def _require_activated(self) -> None:
|
|
90
|
+
"""Raise an error if the gripper has not been activated."""
|
|
91
|
+
if not self._activated:
|
|
92
|
+
raise GripperError(
|
|
93
|
+
"Gripper is not activated. Call activate() before "
|
|
94
|
+
"using open(), close(), or set_position()."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def _build_script(self, function_call: str) -> str:
|
|
98
|
+
"""Build a complete URScript: preamble + config + function call.
|
|
99
|
+
|
|
100
|
+
The preamble defines all ``rq_*`` functions and initializes
|
|
101
|
+
socket state. Two config lines override the mm range for
|
|
102
|
+
the current gripper model before the function executes.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
function_call: A preamble function call (e.g. ``rq_open_and_wait()``).
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Complete URScript code string.
|
|
109
|
+
"""
|
|
110
|
+
return (
|
|
111
|
+
ROBOTIQ_PREAMBLE
|
|
112
|
+
+ f"set_closed_mm(0.0, 1)\n"
|
|
113
|
+
+ f"set_open_mm({self._max_mm}.0, 1)\n"
|
|
114
|
+
+ f"{function_call}\n"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def _send_script(self, code: str) -> None:
|
|
118
|
+
"""Send URScript code via sendCustomScriptFunction.
|
|
119
|
+
|
|
120
|
+
Blocks until the script finishes executing on the robot.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
code: URScript code to execute on the robot.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
GripperError: If the script fails to send.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
ok = self._rtde.sendCustomScriptFunction("_gripper_cmd", code)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
raise GripperError(f"Failed to send gripper script: {e}") from e
|
|
132
|
+
if not ok:
|
|
133
|
+
raise GripperError(
|
|
134
|
+
"sendCustomScriptFunction returned False — "
|
|
135
|
+
"RTDE custom script client may not be available."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# ------------------------------------------------------------------
|
|
139
|
+
# Public interface
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
def activate(self, *, timeout: float = 5.0) -> None:
|
|
143
|
+
"""Activate the gripper and open it to a known safe state.
|
|
144
|
+
|
|
145
|
+
Checks the gripper's actual activation state on the robot using
|
|
146
|
+
the preamble's ``rq_is_gripper_activated()``. Only sends the
|
|
147
|
+
activation command if the gripper is not already activated. If
|
|
148
|
+
already activated, opens the gripper to a known safe state.
|
|
149
|
+
|
|
150
|
+
Safe to call multiple times — skips activation if the gripper
|
|
151
|
+
is already active on the robot.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
timeout: Maximum seconds to wait for activation (default
|
|
155
|
+
5.0). The Robotiq preamble blocks indefinitely if no
|
|
156
|
+
gripper is connected, so this prevents hanging.
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
GripperError: If activation fails or times out.
|
|
160
|
+
"""
|
|
161
|
+
if self._activated:
|
|
162
|
+
return
|
|
163
|
+
# Check activation state and activate+open if needed, all in one script.
|
|
164
|
+
# rq_activate_and_wait() sends ACT then polls until the gripper
|
|
165
|
+
# reports activated (~3-5s for internal initialization).
|
|
166
|
+
activation_script = (
|
|
167
|
+
ROBOTIQ_PREAMBLE
|
|
168
|
+
+ f"set_closed_mm(0.0, 1)\n"
|
|
169
|
+
+ f"set_open_mm({self._max_mm}.0, 1)\n"
|
|
170
|
+
+ "if (not rq_is_gripper_activated()):\n"
|
|
171
|
+
+ " rq_activate_and_wait()\n"
|
|
172
|
+
+ "end\n"
|
|
173
|
+
+ "rq_open_and_wait()\n"
|
|
174
|
+
)
|
|
175
|
+
# Run in a thread with a timeout — the preamble blocks indefinitely
|
|
176
|
+
# if no gripper is physically connected (2000-iteration loop).
|
|
177
|
+
_ready = threading.Event()
|
|
178
|
+
_err: Exception | None = None
|
|
179
|
+
|
|
180
|
+
def _do_activate() -> None:
|
|
181
|
+
try:
|
|
182
|
+
self._send_script(activation_script)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
_err = e
|
|
185
|
+
finally:
|
|
186
|
+
_ready.set()
|
|
187
|
+
|
|
188
|
+
threading.Thread(target=_do_activate, daemon=True).start()
|
|
189
|
+
if not _ready.wait(timeout=timeout):
|
|
190
|
+
raise GripperError(
|
|
191
|
+
f"Gripper activation timed out after {timeout:.0f}s — "
|
|
192
|
+
"check that the gripper is physically connected and powered."
|
|
193
|
+
)
|
|
194
|
+
if _err is not None:
|
|
195
|
+
raise GripperError(f"Gripper activation failed: {_err}") from _err
|
|
196
|
+
|
|
197
|
+
self._activated = True
|
|
198
|
+
logger.info("Robotiq gripper activated (checked robot state)")
|
|
199
|
+
|
|
200
|
+
def is_activated(self) -> bool:
|
|
201
|
+
"""Check if the gripper has been activated."""
|
|
202
|
+
return self._activated
|
|
203
|
+
|
|
204
|
+
def deactivate(self) -> None:
|
|
205
|
+
"""Deactivate the gripper (send DEACT command).
|
|
206
|
+
|
|
207
|
+
Safe to call when not activated — becomes a no-op.
|
|
208
|
+
Use this before enabling freedrive to prevent the
|
|
209
|
+
gripper's script from interfering with manual movement.
|
|
210
|
+
Call activate() again after freedrive is disabled.
|
|
211
|
+
"""
|
|
212
|
+
if not self._activated:
|
|
213
|
+
return
|
|
214
|
+
deact_script = (
|
|
215
|
+
'socket_open("127.0.0.1", 63352, "deact_sock")\n'
|
|
216
|
+
"sync()\n"
|
|
217
|
+
'socket_set_var("DEACT", 1, "deact_sock")\n'
|
|
218
|
+
"sync()\n"
|
|
219
|
+
)
|
|
220
|
+
self._send_script(deact_script)
|
|
221
|
+
self._activated = False
|
|
222
|
+
logger.info("Robotiq gripper deactivated")
|
|
223
|
+
|
|
224
|
+
def open(self, *, wait: bool = True) -> None:
|
|
225
|
+
"""Open the gripper (fully open).
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
wait: If True, block until the gripper reaches the open
|
|
229
|
+
position (default True). If False, return immediately.
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
GripperError: If the gripper has not been activated.
|
|
233
|
+
"""
|
|
234
|
+
self._require_activated()
|
|
235
|
+
func = "rq_open_and_wait()" if wait else "rq_open()"
|
|
236
|
+
self._send_script(self._build_script(func))
|
|
237
|
+
logger.debug("Robotiq gripper opened (wait=%s)", wait)
|
|
238
|
+
|
|
239
|
+
def close(self, *, wait: bool = True) -> None:
|
|
240
|
+
"""Close the gripper (fully closed).
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
wait: If True, block until the gripper reaches the closed
|
|
244
|
+
position or detects contact (default True). If False,
|
|
245
|
+
return immediately.
|
|
246
|
+
|
|
247
|
+
Raises:
|
|
248
|
+
GripperError: If the gripper has not been activated.
|
|
249
|
+
"""
|
|
250
|
+
self._require_activated()
|
|
251
|
+
func = "rq_close_and_wait()" if wait else "rq_close()"
|
|
252
|
+
self._send_script(self._build_script(func))
|
|
253
|
+
logger.debug("Robotiq gripper closed (wait=%s)", wait)
|
|
254
|
+
|
|
255
|
+
def set_position(self, mm: float, *, wait: bool = True) -> None:
|
|
256
|
+
"""Set the gripper to a specific opening in millimeters.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
mm: Opening in mm (0 = fully closed, ``max_mm`` = fully open).
|
|
260
|
+
wait: If True, block until the gripper reaches the target
|
|
261
|
+
position or detects contact (default True). If False,
|
|
262
|
+
return immediately.
|
|
263
|
+
|
|
264
|
+
Raises:
|
|
265
|
+
GripperError: If the gripper has not been activated or
|
|
266
|
+
position is out of range.
|
|
267
|
+
"""
|
|
268
|
+
if not 0 <= mm <= self._max_mm:
|
|
269
|
+
raise GripperError(
|
|
270
|
+
f"Robotiq gripper position must be 0-{self._max_mm} mm, got {mm}."
|
|
271
|
+
)
|
|
272
|
+
self._require_activated()
|
|
273
|
+
if wait:
|
|
274
|
+
func = f"rq_move_and_wait_mm({mm})"
|
|
275
|
+
else:
|
|
276
|
+
func = f"rq_move_mm({mm})"
|
|
277
|
+
self._send_script(self._build_script(func))
|
|
278
|
+
logger.debug("Robotiq gripper set to %.1f mm (wait=%s)", mm, wait)
|
|
279
|
+
|
|
280
|
+
def set_force(self, force: int) -> None:
|
|
281
|
+
"""Set gripper force for subsequent movements.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
force: Force 0-100.
|
|
285
|
+
"""
|
|
286
|
+
if not 0 <= force <= 100:
|
|
287
|
+
raise GripperError(
|
|
288
|
+
f"Robotiq gripper force must be 0-100, got {force}."
|
|
289
|
+
)
|
|
290
|
+
self._force = force
|
|
291
|
+
logger.debug("Robotiq gripper force set to %d", force)
|
|
292
|
+
|
|
293
|
+
def set_speed(self, speed: int) -> None:
|
|
294
|
+
"""Set gripper speed for subsequent movements.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
speed: Speed 0-100.
|
|
298
|
+
"""
|
|
299
|
+
if not 0 <= speed <= 100:
|
|
300
|
+
raise GripperError(
|
|
301
|
+
f"Robotiq gripper speed must be 0-100, got {speed}."
|
|
302
|
+
)
|
|
303
|
+
self._speed = speed
|
|
304
|
+
logger.debug("Robotiq gripper speed set to %d", speed)
|
|
305
|
+
|
|
306
|
+
def is_connected(self) -> bool:
|
|
307
|
+
"""Check if the gripper can send commands via RTDE."""
|
|
308
|
+
try:
|
|
309
|
+
return self._rtde.sendCustomScriptFunction(
|
|
310
|
+
"_grip_ping", 'textmsg("gripper", "ok")'
|
|
311
|
+
)
|
|
312
|
+
except Exception:
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
def disconnect(self) -> None:
|
|
316
|
+
"""Disconnect the gripper.
|
|
317
|
+
|
|
318
|
+
Resets the activation flag. Call activate() again before
|
|
319
|
+
using the gripper.
|
|
320
|
+
"""
|
|
321
|
+
self._activated = False
|
|
322
|
+
logger.debug("Robotiq gripper disconnected")
|