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/motion.py
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""Motion commands for Universal Robots e-Series.
|
|
2
|
+
|
|
3
|
+
Provides joint moves (moveJ), linear moves (moveL), and relative
|
|
4
|
+
Cartesian moves via the RTDE control interface. All methods support per-call
|
|
5
|
+
velocity and acceleration override.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from contextlib import contextmanager
|
|
14
|
+
from enum import IntEnum
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
from urkit.exceptions import MotionError
|
|
18
|
+
from urkit.geometry import MoveFrame, transform_pose_delta
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@contextmanager
|
|
22
|
+
def _suppress_rtde_stderr():
|
|
23
|
+
"""Temporarily redirect stderr to /dev/null.
|
|
24
|
+
|
|
25
|
+
The ur_rtde C++ library prints "RTDE control script is not running!"
|
|
26
|
+
to raw stderr (fd 2) on every call when the script is stopped.
|
|
27
|
+
This context manager suppresses those messages by redirecting fd 2
|
|
28
|
+
around the call.
|
|
29
|
+
"""
|
|
30
|
+
devnull = os.open(os.devnull, os.O_WRONLY)
|
|
31
|
+
old_stderr = os.dup(2)
|
|
32
|
+
try:
|
|
33
|
+
os.dup2(devnull, 2)
|
|
34
|
+
sys.stderr.flush()
|
|
35
|
+
yield
|
|
36
|
+
finally:
|
|
37
|
+
os.dup2(old_stderr, 2)
|
|
38
|
+
os.close(old_stderr)
|
|
39
|
+
os.close(devnull)
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from rtde_control import RTDEControlInterface
|
|
43
|
+
from rtde_io import RTDEIOInterface
|
|
44
|
+
from rtde_receive import RTDEReceiveInterface
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
# URScript axis order for delta move commands:
|
|
49
|
+
# 0=X, 1=Y, 2=Z, 3=Roll, 4=Pitch, 5=Yaw
|
|
50
|
+
_AXIS_LABELS = ["X", "Y", "Z", "Roll", "Pitch", "Yaw"]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class FreedriveMode(IntEnum):
|
|
54
|
+
"""Freedrive axis selection mode.
|
|
55
|
+
|
|
56
|
+
Controls which axes the robot allows manual movement on.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
ALL = 0 # All 6 axes free
|
|
60
|
+
XYZ = 1 # Only linear axes (X, Y, Z)
|
|
61
|
+
ROTATION = 2 # Only rotational axes (Roll, Pitch, Yaw)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Motion:
|
|
65
|
+
"""Motion commands via RTDE control interface.
|
|
66
|
+
|
|
67
|
+
Wraps the RTDEControlInterface with typed, documented motion
|
|
68
|
+
primitives. All methods raise MotionError on failure.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
rtde_control: RTDEControlInterface instance.
|
|
72
|
+
rtde_receive: RTDEReceiveInterface instance (for current pose reads).
|
|
73
|
+
rtde_io: RTDEIOInterface instance (for speed slider control).
|
|
74
|
+
default_vel: Default linear velocity (m/s) for move commands.
|
|
75
|
+
default_acc: Default linear acceleration (m/s²) for move commands.
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
>>> motion = Motion(rtde_c, rtde_r, rtde_io, default_vel=0.5, default_acc=0.3)
|
|
79
|
+
>>> motion.movej([0, -1.57, 0, -1.57, 0, 0])
|
|
80
|
+
>>> motion.movel([0.5, 0, 0.3, 0, 0, 0])
|
|
81
|
+
>>> motion.move_by([0.01, 0, 0, 0, 0, 0])
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
rtde_control: "RTDEControlInterface",
|
|
87
|
+
rtde_receive: "RTDEReceiveInterface",
|
|
88
|
+
rtde_io: "RTDEIOInterface",
|
|
89
|
+
default_vel: float = 0.5,
|
|
90
|
+
default_acc: float = 0.3,
|
|
91
|
+
) -> None:
|
|
92
|
+
if default_vel <= 0:
|
|
93
|
+
raise MotionError(
|
|
94
|
+
f"default_vel must be > 0, got {default_vel}."
|
|
95
|
+
)
|
|
96
|
+
if default_acc <= 0:
|
|
97
|
+
raise MotionError(
|
|
98
|
+
f"default_acc must be > 0, got {default_acc}."
|
|
99
|
+
)
|
|
100
|
+
self._rtde_c = rtde_control
|
|
101
|
+
self._rtde_r = rtde_receive
|
|
102
|
+
self._rtde_io = rtde_io
|
|
103
|
+
self._default_vel = default_vel
|
|
104
|
+
self._default_acc = default_acc
|
|
105
|
+
self._freedrive_active = False
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def is_freedrive_active(self) -> bool:
|
|
109
|
+
"""Return whether freedrive is currently active."""
|
|
110
|
+
return self._freedrive_active
|
|
111
|
+
|
|
112
|
+
def movej(
|
|
113
|
+
self,
|
|
114
|
+
joints: list[float],
|
|
115
|
+
vel: float | None = None,
|
|
116
|
+
acc: float | None = None,
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Move to target joint configuration.
|
|
119
|
+
|
|
120
|
+
Uses URScript movej() for joint-space interpolation.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
joints: 6 joint angles in radians [j0, j1, j2, j3, j4, j5].
|
|
124
|
+
vel: Linear velocity (m/s). Falls back to default_vel.
|
|
125
|
+
acc: Linear acceleration (m/s²). Falls back to default_acc.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
MotionError: If the move fails.
|
|
129
|
+
"""
|
|
130
|
+
if len(joints) != 6:
|
|
131
|
+
raise MotionError(
|
|
132
|
+
f"Joint move requires 6 values, got {len(joints)}."
|
|
133
|
+
)
|
|
134
|
+
vel = vel if vel is not None else self._default_vel
|
|
135
|
+
acc = acc if acc is not None else self._default_acc
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
if not self._rtde_c.isConnected():
|
|
139
|
+
raise MotionError(
|
|
140
|
+
"RTDE connection lost. The robot may have faulted. "
|
|
141
|
+
"Reconnect or reinitialize the robot."
|
|
142
|
+
)
|
|
143
|
+
logger.debug(
|
|
144
|
+
"movej: joints=%s, vel=%.3f, acc=%.3f", joints, vel, acc
|
|
145
|
+
)
|
|
146
|
+
with _suppress_rtde_stderr():
|
|
147
|
+
self._rtde_c.moveJ(joints, vel, acc)
|
|
148
|
+
except MotionError:
|
|
149
|
+
raise
|
|
150
|
+
except Exception as e:
|
|
151
|
+
raise MotionError(
|
|
152
|
+
f"Joint move failed: {e}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def movel(
|
|
156
|
+
self,
|
|
157
|
+
pose: list[float],
|
|
158
|
+
vel: float | None = None,
|
|
159
|
+
acc: float | None = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Move in a straight line to target TCP pose.
|
|
162
|
+
|
|
163
|
+
Uses URScript movel() for Cartesian linear interpolation.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
pose: 6 floats [x, y, z, rx, ry, rz] in meters/radians.
|
|
167
|
+
vel: Linear velocity (m/s). Falls back to default_vel.
|
|
168
|
+
acc: Linear acceleration (m/s²). Falls back to default_acc.
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
MotionError: If the move fails.
|
|
172
|
+
"""
|
|
173
|
+
if len(pose) != 6:
|
|
174
|
+
raise MotionError(
|
|
175
|
+
f"Linear move requires 6 values [x,y,z,rx,ry,rz], got {len(pose)}."
|
|
176
|
+
)
|
|
177
|
+
vel = vel if vel is not None else self._default_vel
|
|
178
|
+
acc = acc if acc is not None else self._default_acc
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
if not self._rtde_c.isConnected():
|
|
182
|
+
raise MotionError(
|
|
183
|
+
"RTDE connection lost. The robot may have faulted. "
|
|
184
|
+
"Reconnect or reinitialize the robot."
|
|
185
|
+
)
|
|
186
|
+
logger.debug(
|
|
187
|
+
"movel: pose=%s, vel=%.3f, acc=%.3f", pose, vel, acc
|
|
188
|
+
)
|
|
189
|
+
with _suppress_rtde_stderr():
|
|
190
|
+
self._rtde_c.moveL(pose, vel, acc)
|
|
191
|
+
except MotionError:
|
|
192
|
+
raise
|
|
193
|
+
except Exception as e:
|
|
194
|
+
raise MotionError(
|
|
195
|
+
f"Linear move failed: {e}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def move_by(
|
|
199
|
+
self,
|
|
200
|
+
delta: list[float],
|
|
201
|
+
vel: float | None = None,
|
|
202
|
+
acc: float | None = None,
|
|
203
|
+
frame: MoveFrame = MoveFrame.BASE,
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Execute a relative Cartesian linear move.
|
|
206
|
+
|
|
207
|
+
Reads the current TCP pose, transforms the delta according to
|
|
208
|
+
the given frame of reference, and moves linearly to the
|
|
209
|
+
resulting pose.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
delta: 6 floats [dx, dy, dz, droll, dpitch, dyaw].
|
|
213
|
+
vel: Linear velocity (m/s). Falls back to default_vel.
|
|
214
|
+
acc: Linear acceleration (m/s²). Falls back to default_acc.
|
|
215
|
+
frame: Coordinate frame for interpreting the delta
|
|
216
|
+
(``MoveFrame.BASE`` or ``MoveFrame.TOOL``).
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
MotionError: If the move fails.
|
|
220
|
+
"""
|
|
221
|
+
if len(delta) != 6:
|
|
222
|
+
raise MotionError(
|
|
223
|
+
f"Relative move requires 6 values [dx,dy,dz,droll,dpitch,dyaw], "
|
|
224
|
+
f"got {len(delta)}."
|
|
225
|
+
)
|
|
226
|
+
vel = vel if vel is not None else self._default_vel
|
|
227
|
+
acc = acc if acc is not None else self._default_acc
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
if not self._rtde_c.isConnected():
|
|
231
|
+
raise MotionError(
|
|
232
|
+
"RTDE connection lost. The robot may have faulted. "
|
|
233
|
+
"Reconnect or reinitialize the robot."
|
|
234
|
+
)
|
|
235
|
+
current = list(self._rtde_r.getActualTCPPose())
|
|
236
|
+
target = transform_pose_delta(current, delta, frame)
|
|
237
|
+
logger.debug(
|
|
238
|
+
"move_by: current=%s, target=%s, frame=%s, vel=%.3f, acc=%.3f",
|
|
239
|
+
current, target, frame.name, vel, acc,
|
|
240
|
+
)
|
|
241
|
+
with _suppress_rtde_stderr():
|
|
242
|
+
self._rtde_c.moveL(target, vel, acc)
|
|
243
|
+
except MotionError:
|
|
244
|
+
raise
|
|
245
|
+
except Exception as e:
|
|
246
|
+
raise MotionError(
|
|
247
|
+
f"Relative move failed: {e}"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def move_until_contact(
|
|
251
|
+
self,
|
|
252
|
+
speed_vector: list[float],
|
|
253
|
+
*,
|
|
254
|
+
threshold: float = 5.0,
|
|
255
|
+
acceleration: float = 0.1,
|
|
256
|
+
) -> None:
|
|
257
|
+
"""Move until contact is detected via TCP force sensing.
|
|
258
|
+
|
|
259
|
+
Runs a 500 Hz control loop that sends ``speedL()`` commands and
|
|
260
|
+
checks the tool force/torque on every iteration. Unlike the
|
|
261
|
+
native ``moveUntilContact`` URScript command, this wrapper is
|
|
262
|
+
interruptible with ``KeyboardInterrupt`` (Ctrl+C) because the
|
|
263
|
+
contact check happens in Python each cycle.
|
|
264
|
+
|
|
265
|
+
Accepts a full 6-element speed vector so the caller controls
|
|
266
|
+
every axis — linear and rotational.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
speed_vector: 6-element speed vector
|
|
270
|
+
``[vx, vy, vz, vRoll, vPitch, dYaw]`` in m/s and rad/s.
|
|
271
|
+
threshold: Force/torque delta (N or Nm) that triggers contact.
|
|
272
|
+
Contact fires when any of the 6 wrench components changes
|
|
273
|
+
by more than this value from the baseline reading.
|
|
274
|
+
acceleration: Acceleration limit passed to ``speedL()`` in m/s².
|
|
275
|
+
|
|
276
|
+
Raises:
|
|
277
|
+
MotionError: If the command fails or the vector is invalid.
|
|
278
|
+
|
|
279
|
+
Example:
|
|
280
|
+
>>> # Move straight down until contact
|
|
281
|
+
>>> motion.move_until_contact([0, 0, -0.02, 0, 0, 0])
|
|
282
|
+
>>> # Move down while rotating, higher threshold
|
|
283
|
+
>>> motion.move_until_contact([0, 0, -0.02, 0, 0.1, 0], threshold=10.0)
|
|
284
|
+
"""
|
|
285
|
+
if len(speed_vector) != 6:
|
|
286
|
+
raise MotionError(
|
|
287
|
+
f"Speed vector must have 6 values, got {len(speed_vector)}."
|
|
288
|
+
)
|
|
289
|
+
if threshold <= 0:
|
|
290
|
+
raise MotionError(f"Threshold must be > 0, got {threshold}.")
|
|
291
|
+
|
|
292
|
+
try:
|
|
293
|
+
logger.debug(
|
|
294
|
+
"move_until_contact: speed_vector=%s, threshold=%.2f",
|
|
295
|
+
speed_vector, threshold,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Baseline force reading before the loop
|
|
299
|
+
baseline = list(self._rtde_r.getActualTCPForce())
|
|
300
|
+
|
|
301
|
+
while True:
|
|
302
|
+
if not self._rtde_c.isConnected():
|
|
303
|
+
raise MotionError(
|
|
304
|
+
"RTDE connection lost during move_until_contact. "
|
|
305
|
+
"The robot may have faulted or the script stopped."
|
|
306
|
+
)
|
|
307
|
+
# Check robot safety state (cached data, non-blocking)
|
|
308
|
+
try:
|
|
309
|
+
if self._rtde_r.isProtectiveStopped():
|
|
310
|
+
raise MotionError(
|
|
311
|
+
"Robot is in protective stop. "
|
|
312
|
+
"Clear the stop and reinitialize."
|
|
313
|
+
)
|
|
314
|
+
if self._rtde_r.isEmergencyStopped():
|
|
315
|
+
raise MotionError(
|
|
316
|
+
"Robot is in emergency stop. "
|
|
317
|
+
"Clear the stop and reinitialize."
|
|
318
|
+
)
|
|
319
|
+
except MotionError:
|
|
320
|
+
raise
|
|
321
|
+
except Exception:
|
|
322
|
+
pass # Telemetry read failed, continue
|
|
323
|
+
|
|
324
|
+
with _suppress_rtde_stderr():
|
|
325
|
+
t_start = self._rtde_c.initPeriod()
|
|
326
|
+
self._rtde_c.speedL(speed_vector, acceleration, 0.002)
|
|
327
|
+
|
|
328
|
+
# Check force/torque for contact
|
|
329
|
+
current = list(self._rtde_r.getActualTCPForce())
|
|
330
|
+
if any(
|
|
331
|
+
abs(current[i] - baseline[i]) > threshold for i in range(6)
|
|
332
|
+
):
|
|
333
|
+
break
|
|
334
|
+
|
|
335
|
+
with _suppress_rtde_stderr():
|
|
336
|
+
self._rtde_c.waitPeriod(t_start)
|
|
337
|
+
except MotionError:
|
|
338
|
+
raise
|
|
339
|
+
except Exception as e:
|
|
340
|
+
raise MotionError(f"move_until_contact failed: {e}")
|
|
341
|
+
finally:
|
|
342
|
+
try:
|
|
343
|
+
with _suppress_rtde_stderr():
|
|
344
|
+
self._rtde_c.speedStop()
|
|
345
|
+
except Exception:
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
def move_velocity(
|
|
349
|
+
self,
|
|
350
|
+
speed_vector: list[float],
|
|
351
|
+
duration: float,
|
|
352
|
+
acceleration: float = 0.1,
|
|
353
|
+
dt: float = 0.002,
|
|
354
|
+
) -> None:
|
|
355
|
+
"""Move at a constant Cartesian velocity for a given duration.
|
|
356
|
+
|
|
357
|
+
Runs a 500 Hz control loop that sends ``speedL()`` commands.
|
|
358
|
+
Blocks until *duration* seconds have elapsed.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
speed_vector: 6-element Cartesian velocity
|
|
362
|
+
``[vx, vy, vz, vRoll, vPitch, dYaw]`` in m/s and rad/s.
|
|
363
|
+
duration: How long to move in seconds.
|
|
364
|
+
acceleration: Acceleration limit in m/s\ :sup:2.
|
|
365
|
+
dt: Control loop period in seconds (default 0.002 = 500 Hz).
|
|
366
|
+
|
|
367
|
+
Raises:
|
|
368
|
+
MotionError: If the command fails.
|
|
369
|
+
|
|
370
|
+
Example:
|
|
371
|
+
>>> # Move down at 20 mm/s for 1 second
|
|
372
|
+
>>> motion.move_velocity([0, 0, -0.02, 0, 0, 0], duration=1.0)
|
|
373
|
+
"""
|
|
374
|
+
if len(speed_vector) != 6:
|
|
375
|
+
raise MotionError(
|
|
376
|
+
f"Speed vector must have 6 values, got {len(speed_vector)}."
|
|
377
|
+
)
|
|
378
|
+
if duration <= 0:
|
|
379
|
+
raise MotionError(f"Duration must be > 0, got {duration}.")
|
|
380
|
+
|
|
381
|
+
try:
|
|
382
|
+
import time as _time
|
|
383
|
+
|
|
384
|
+
start = _time.monotonic()
|
|
385
|
+
while True:
|
|
386
|
+
elapsed = _time.monotonic() - start
|
|
387
|
+
if elapsed >= duration:
|
|
388
|
+
break
|
|
389
|
+
if not self._rtde_c.isConnected():
|
|
390
|
+
raise MotionError(
|
|
391
|
+
"RTDE connection lost during move_velocity. "
|
|
392
|
+
"The robot may have faulted or the script stopped."
|
|
393
|
+
)
|
|
394
|
+
# Check robot safety state (cached data, non-blocking)
|
|
395
|
+
try:
|
|
396
|
+
if self._rtde_r.isProtectiveStopped():
|
|
397
|
+
raise MotionError(
|
|
398
|
+
"Robot is in protective stop. "
|
|
399
|
+
"Clear the stop and reinitialize."
|
|
400
|
+
)
|
|
401
|
+
if self._rtde_r.isEmergencyStopped():
|
|
402
|
+
raise MotionError(
|
|
403
|
+
"Robot is in emergency stop. "
|
|
404
|
+
"Clear the stop and reinitialize."
|
|
405
|
+
)
|
|
406
|
+
except MotionError:
|
|
407
|
+
raise
|
|
408
|
+
except Exception:
|
|
409
|
+
pass # Telemetry read failed, continue
|
|
410
|
+
|
|
411
|
+
with _suppress_rtde_stderr():
|
|
412
|
+
t_start = self._rtde_c.initPeriod()
|
|
413
|
+
self._rtde_c.speedL(speed_vector, acceleration, dt)
|
|
414
|
+
self._rtde_c.waitPeriod(t_start)
|
|
415
|
+
except MotionError:
|
|
416
|
+
raise
|
|
417
|
+
except Exception as e:
|
|
418
|
+
raise MotionError(f"move_velocity failed: {e}")
|
|
419
|
+
finally:
|
|
420
|
+
try:
|
|
421
|
+
with _suppress_rtde_stderr():
|
|
422
|
+
self._rtde_c.speedStop()
|
|
423
|
+
except Exception:
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
def speed_stop(self) -> None:
|
|
427
|
+
"""Stop any ongoing speed motion immediately.
|
|
428
|
+
|
|
429
|
+
Used to halt speed-based operations.
|
|
430
|
+
"""
|
|
431
|
+
try:
|
|
432
|
+
with _suppress_rtde_stderr():
|
|
433
|
+
self._rtde_c.speedStop()
|
|
434
|
+
except Exception as e:
|
|
435
|
+
logger.warning("speedStop failed: %s", e)
|
|
436
|
+
|
|
437
|
+
def stop_script(self) -> None:
|
|
438
|
+
"""Stop the running URScript program."""
|
|
439
|
+
try:
|
|
440
|
+
with _suppress_rtde_stderr():
|
|
441
|
+
self._rtde_c.stopScript()
|
|
442
|
+
except Exception as e:
|
|
443
|
+
logger.warning("stopScript failed: %s", e)
|
|
444
|
+
|
|
445
|
+
def set_speed_slider(self, factor: float) -> None:
|
|
446
|
+
"""Set the speed slider factor for subsequent motions.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
factor: Speed factor 0.0–1.0.
|
|
450
|
+
|
|
451
|
+
Raises:
|
|
452
|
+
MotionError: If the setting fails.
|
|
453
|
+
"""
|
|
454
|
+
if not 0.0 <= factor <= 1.0:
|
|
455
|
+
raise MotionError(
|
|
456
|
+
f"Speed slider factor must be 0.0–1.0, got {factor}."
|
|
457
|
+
)
|
|
458
|
+
try:
|
|
459
|
+
self._rtde_io.setSpeedSlider(factor)
|
|
460
|
+
except Exception as e:
|
|
461
|
+
raise MotionError(
|
|
462
|
+
f"Failed to set speed slider to {factor}: {e}"
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
def enable_freedrive(self, mode: FreedriveMode = FreedriveMode.ALL) -> None:
|
|
466
|
+
"""Enable freedrive mode for manual robot manipulation.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
mode: Which axes to allow manual movement on.
|
|
470
|
+
|
|
471
|
+
Raises:
|
|
472
|
+
MotionError: If freedrive cannot be enabled.
|
|
473
|
+
"""
|
|
474
|
+
try:
|
|
475
|
+
if mode == FreedriveMode.ALL:
|
|
476
|
+
free_axes = [1, 1, 1, 1, 1, 1]
|
|
477
|
+
elif mode == FreedriveMode.XYZ:
|
|
478
|
+
free_axes = [1, 1, 1, 0, 0, 0]
|
|
479
|
+
elif mode == FreedriveMode.ROTATION:
|
|
480
|
+
free_axes = [0, 0, 0, 1, 1, 1]
|
|
481
|
+
else:
|
|
482
|
+
raise MotionError(f"Unknown freedrive mode: {mode}")
|
|
483
|
+
|
|
484
|
+
feature = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] # base coordinate frame
|
|
485
|
+
success = self._rtde_c.freedriveMode(free_axes, feature)
|
|
486
|
+
if not success:
|
|
487
|
+
raise MotionError(
|
|
488
|
+
f"freedriveMode returned false (mode={mode.name})"
|
|
489
|
+
)
|
|
490
|
+
self._freedrive_active = True
|
|
491
|
+
logger.info("Freedrive mode enabled (%s)", mode.name)
|
|
492
|
+
except Exception as e:
|
|
493
|
+
raise MotionError(
|
|
494
|
+
f"Failed to enable freedrive mode: {e}"
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
def disable_freedrive(self) -> None:
|
|
498
|
+
"""Disable freedrive mode."""
|
|
499
|
+
try:
|
|
500
|
+
self._rtde_c.endFreedriveMode()
|
|
501
|
+
self._freedrive_active = False
|
|
502
|
+
logger.info("Freedrive mode disabled")
|
|
503
|
+
except Exception as e:
|
|
504
|
+
logger.warning("Failed to disable freedrive: %s", e)
|