urkit 0.4.4__tar.gz → 0.4.6__tar.gz

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.
Files changed (42) hide show
  1. {urkit-0.4.4 → urkit-0.4.6}/PKG-INFO +1 -1
  2. {urkit-0.4.4 → urkit-0.4.6}/pyproject.toml +1 -1
  3. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/__init__.py +1 -1
  4. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/cli/points.py +2 -0
  5. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/cli/teach.py +103 -40
  6. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/cli/terminal.py +108 -1
  7. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/motion.py +36 -6
  8. {urkit-0.4.4 → urkit-0.4.6}/src/urkit.egg-info/PKG-INFO +1 -1
  9. {urkit-0.4.4 → urkit-0.4.6}/README.md +0 -0
  10. {urkit-0.4.4 → urkit-0.4.6}/setup.cfg +0 -0
  11. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/__main__.py +0 -0
  12. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/cli/__init__.py +0 -0
  13. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/cli/colors.py +0 -0
  14. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/cli/connection_monitor.py +0 -0
  15. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/cli/init.py +0 -0
  16. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/config.py +0 -0
  17. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/connection.py +0 -0
  18. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/exceptions.py +0 -0
  19. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/geometry.py +0 -0
  20. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/gripper/__init__.py +0 -0
  21. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/gripper/base.py +0 -0
  22. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/gripper/digital.py +0 -0
  23. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/gripper/presets.py +0 -0
  24. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/gripper/robotiq.py +0 -0
  25. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/gripper/robotiq_preamble.py +0 -0
  26. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/io.py +0 -0
  27. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/points.py +0 -0
  28. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/robot.py +0 -0
  29. {urkit-0.4.4 → urkit-0.4.6}/src/urkit/telemetry.py +0 -0
  30. {urkit-0.4.4 → urkit-0.4.6}/src/urkit.egg-info/SOURCES.txt +0 -0
  31. {urkit-0.4.4 → urkit-0.4.6}/src/urkit.egg-info/dependency_links.txt +0 -0
  32. {urkit-0.4.4 → urkit-0.4.6}/src/urkit.egg-info/entry_points.txt +0 -0
  33. {urkit-0.4.4 → urkit-0.4.6}/src/urkit.egg-info/requires.txt +0 -0
  34. {urkit-0.4.4 → urkit-0.4.6}/src/urkit.egg-info/top_level.txt +0 -0
  35. {urkit-0.4.4 → urkit-0.4.6}/tests/test_exceptions.py +0 -0
  36. {urkit-0.4.4 → urkit-0.4.6}/tests/test_geometry.py +0 -0
  37. {urkit-0.4.4 → urkit-0.4.6}/tests/test_gripper.py +0 -0
  38. {urkit-0.4.4 → urkit-0.4.6}/tests/test_gripper_factory.py +0 -0
  39. {urkit-0.4.4 → urkit-0.4.6}/tests/test_gripper_presets.py +0 -0
  40. {urkit-0.4.4 → urkit-0.4.6}/tests/test_move_sequence.py +0 -0
  41. {urkit-0.4.4 → urkit-0.4.6}/tests/test_points.py +0 -0
  42. {urkit-0.4.4 → urkit-0.4.6}/tests/test_robot_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "urkit"
7
- version = "0.4.4"
7
+ version = "0.4.6"
8
8
  description = "Universal Robots e-Series control toolkit built on ur_rtde"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -24,7 +24,7 @@ Quick start::
24
24
 
25
25
  from __future__ import annotations
26
26
 
27
- __version__ = "0.4.4"
27
+ __version__ = "0.4.6"
28
28
 
29
29
 
30
30
  from urkit.exceptions import (
@@ -16,6 +16,7 @@ from pathlib import Path
16
16
  from rich.console import Console
17
17
  from rich.table import Table
18
18
 
19
+ from urkit import __version__
19
20
  from urkit.config import _load_config as load_config
20
21
  from urkit.cli.colors import blue, cyan, dim, yellow
21
22
  from urkit.cli.terminal import RawTerminal, read_burst, wait_input, warn_if_windows
@@ -158,6 +159,7 @@ def _interactive_points_filter(points_db: Points, all_points: list[str], points_
158
159
  # Clear screen and draw header
159
160
  sys.stdout.write("\033[2J\033[1;1H")
160
161
  sys.stdout.write(cyan(" === POINT EXPLORER ===") + "\n")
162
+ sys.stdout.write(dim(f" urkit {__version__}") + "\n")
161
163
  sys.stdout.write(dim(" Type to search · ↑↓ scroll · Enter reload · ESC quit") + "\n\n")
162
164
  sys.stdout.write(f" {blue('Search:')} {filter_str}\n")
163
165
  if refresh_error:
@@ -19,14 +19,19 @@ import os
19
19
  import signal
20
20
  import sys
21
21
  import time
22
+ import traceback
22
23
  from pathlib import Path
23
24
 
24
25
  import yaml
25
26
 
27
+ from urkit import __version__
26
28
  from urkit.cli.colors import blue, cyan, dim, green, red, yellow
27
29
  from urkit.cli.terminal import (
28
30
  RawTerminal,
31
+ input_active,
29
32
  read_burst,
33
+ start_interrupt_watcher,
34
+ stop_interrupt_watcher,
30
35
  wait_input,
31
36
  warn_if_windows,
32
37
  )
@@ -295,6 +300,7 @@ def _draw_screen(
295
300
  # Header
296
301
  lines.append(dim("=" * width))
297
302
  lines.append(cyan(" === URKit Teach Pendant ===").center(width))
303
+ lines.append(dim(f" urkit {__version__}").center(width))
298
304
  lines.append(cyan(f" IP: {robot.ip}").center(width))
299
305
  if expert_mode:
300
306
  lines.append(yellow(" EXPERT MODE").center(width))
@@ -475,6 +481,7 @@ def _read_input(prompt: str, max_len: int = 30) -> str | None:
475
481
  sys.stdout.flush()
476
482
 
477
483
  name = ""
484
+ input_active.set()
478
485
  try:
479
486
  while True:
480
487
  if not wait_input(0.1):
@@ -511,6 +518,7 @@ def _read_input(prompt: str, max_len: int = 30) -> str | None:
511
518
  break
512
519
  finally:
513
520
  raw.disable()
521
+ input_active.clear()
514
522
 
515
523
  sys.stdout.write("\r\033[K\n")
516
524
  sys.stdout.flush()
@@ -624,7 +632,11 @@ def _filter_select_points(
624
632
 
625
633
  # Wait for input, then read the full burst (e.g. the
626
634
  # 3-byte \x1b[A arrow-key sequence) in one go.
627
- raw_bytes = read_burst(0.1)
635
+ input_active.set()
636
+ try:
637
+ raw_bytes = read_burst(0.1)
638
+ finally:
639
+ input_active.clear()
628
640
  if not raw_bytes:
629
641
  if _cli_monitor and _cli_monitor.fault_detected:
630
642
  raise URKitConnectionError(
@@ -739,11 +751,15 @@ def _submenu_goto_point(
739
751
  robot, name, mode_label, start_pose, target_pose, is_cartesian, max_progress
740
752
  )
741
753
  max_progress = pct
742
- if wait_input(0.05):
743
- key = read_burst(0.0).decode("ascii", errors="replace")
744
- if " " in key: # Space to cancel
745
- cancelled = True
746
- break
754
+ input_active.set()
755
+ try:
756
+ if wait_input(0.05):
757
+ key = read_burst(0.0).decode("ascii", errors="replace")
758
+ if " " in key: # Space to cancel
759
+ cancelled = True
760
+ break
761
+ finally:
762
+ input_active.clear()
747
763
 
748
764
  # Clear moving screen
749
765
  sys.stdout.write("\033[2J\033[1;1H")
@@ -761,6 +777,9 @@ def _submenu_goto_point(
761
777
  except URKitConnectionError:
762
778
  raise
763
779
  except Exception as e:
780
+ logger.debug(
781
+ "goto move failed: %s\n%s", e, traceback.format_exc()
782
+ )
764
783
  messages.append(f"Error: {e}")
765
784
 
766
785
 
@@ -903,7 +922,11 @@ def _submenu_orient_tcp(
903
922
  sys.stdout.flush()
904
923
  needs_redraw = False
905
924
 
906
- raw_bytes = read_burst(0.1)
925
+ input_active.set()
926
+ try:
927
+ raw_bytes = read_burst(0.1)
928
+ finally:
929
+ input_active.clear()
907
930
  if not raw_bytes:
908
931
  if _cli_monitor and _cli_monitor.fault_detected:
909
932
  raise URKitConnectionError(
@@ -1022,7 +1045,11 @@ def _submenu_freedrive_axes(
1022
1045
  sys.stdout.flush()
1023
1046
  needs_redraw = False
1024
1047
 
1025
- raw_bytes = read_burst(0.1)
1048
+ input_active.set()
1049
+ try:
1050
+ raw_bytes = read_burst(0.1)
1051
+ finally:
1052
+ input_active.clear()
1026
1053
  if not raw_bytes:
1027
1054
  continue
1028
1055
  text = raw_bytes.decode("ascii", errors="replace")
@@ -1183,12 +1210,12 @@ def _teach_pendant(
1183
1210
  print("Error: stdin is not a terminal. Run from an interactive shell.")
1184
1211
  sys.exit(1)
1185
1212
 
1186
- # SIGINT handler: restore terminal and exit when Ctrl+C pressed.
1187
- # On Unix, raw mode keeps ISIG enabled, so Ctrl+C generates SIGINT.
1188
- # On Windows, Ctrl+C arrives as a raw 0x03 byte instead (handled by
1189
- # the main loop's exit key path). os._exit() works even when blocked
1213
+ # Best-effort robot stop and terminal restore, then exit.
1214
+ # Runs from the SIGINT handler (Unix Ctrl+C) and from the
1215
+ # interrupt watcher (Windows Ctrl+C / fault while the main thread
1216
+ # is blocked in a robot call). os._exit() works even when blocked
1190
1217
  # in C library code.
1191
- def _sigint_handler(signum: int, frame: object) -> None:
1218
+ def _interrupt_exit(reason: str) -> None:
1192
1219
  try:
1193
1220
  if state["freedrive"]:
1194
1221
  robot.disable_freedrive()
@@ -1207,9 +1234,16 @@ def _teach_pendant(
1207
1234
  _restore_terminal()
1208
1235
  except Exception:
1209
1236
  pass
1210
- sys.stderr.write("\nInterrupted.\n")
1237
+ sys.stderr.write(f"\n{reason}\n")
1211
1238
  os._exit(0)
1212
1239
 
1240
+ # SIGINT handler: restore terminal and exit when Ctrl+C pressed.
1241
+ # On Unix, raw mode keeps ISIG enabled, so Ctrl+C generates SIGINT.
1242
+ # On Windows, Ctrl+C arrives as a raw 0x03 byte instead (handled by
1243
+ # the main loop's exit key path and the interrupt watcher).
1244
+ def _sigint_handler(signum: int, frame: object) -> None:
1245
+ _interrupt_exit("Interrupted.")
1246
+
1213
1247
  signal.signal(signal.SIGINT, _sigint_handler)
1214
1248
 
1215
1249
  # Connection watchdog: detects faults and interrupts blocking calls.
@@ -1225,6 +1259,21 @@ def _teach_pendant(
1225
1259
  global _cli_monitor
1226
1260
  _cli_monitor = monitor # used by _read_input and _filter_select_points
1227
1261
 
1262
+ # Windows escape hatch: no SIGINT delivery and no SIGALRM, so while
1263
+ # the main thread is blocked in a long RTDE call (e.g. a move while
1264
+ # a key is held) neither Ctrl+C (a console byte) nor a detected
1265
+ # fault can reach it. The watcher fires the same cleanup and exit.
1266
+ def _watcher_fault_reason() -> str | None:
1267
+ if monitor.fault_detected:
1268
+ return (
1269
+ f"Robot fault detected: "
1270
+ f"{monitor._reason or 'RTDE connection lost'}. "
1271
+ "RTDE connection lost."
1272
+ )
1273
+ return None
1274
+
1275
+ start_interrupt_watcher(_interrupt_exit, _watcher_fault_reason)
1276
+
1228
1277
  try:
1229
1278
  try:
1230
1279
  _configure_terminal()
@@ -1239,39 +1288,49 @@ def _teach_pendant(
1239
1288
  command_handled = False
1240
1289
 
1241
1290
  # --- Wait for a key ---
1242
- while True:
1243
- if wait_input(0.05):
1244
- break
1245
-
1246
- # Periodic refresh while in freedrive — robot may be moving by hand
1247
- if state["freedrive"]:
1248
- now = time.monotonic()
1249
- if now - last_refresh >= _FREEDRIVE_REFRESH_S:
1250
- last_refresh = now
1251
- _draw_screen(robot, state, messages, expert_mode)
1252
-
1253
- # Check for fault detected by watchdog thread
1254
- if monitor.fault_detected:
1255
- raise URKitConnectionError(
1256
- f"Robot fault detected: {monitor._reason or 'RTDE connection lost'}. "
1257
- "RTDE connection lost."
1258
- )
1291
+ # input_active tells the Windows interrupt watcher to
1292
+ # stay out of the console queue while we read.
1293
+ input_active.set()
1294
+ try:
1295
+ while True:
1296
+ if wait_input(0.05):
1297
+ break
1298
+
1299
+ # Periodic refresh while in freedrive — robot may be moving by hand
1300
+ if state["freedrive"]:
1301
+ now = time.monotonic()
1302
+ if now - last_refresh >= _FREEDRIVE_REFRESH_S:
1303
+ last_refresh = now
1304
+ _draw_screen(robot, state, messages, expert_mode)
1305
+
1306
+ # Check for fault detected by watchdog thread
1307
+ if monitor.fault_detected:
1308
+ raise URKitConnectionError(
1309
+ f"Robot fault detected: {monitor._reason or 'RTDE connection lost'}. "
1310
+ "RTDE connection lost."
1311
+ )
1259
1312
 
1260
- # --- Read key, draining buffered repeats ---
1261
- # When a key is held, the terminal auto-repeats it into the
1262
- # input buffer. Reading only one character leaves stale repeats
1263
- # behind — when the user switches from one movement key to
1264
- # another, the last buffered copy of the old key fires one final
1265
- # move in the wrong direction before the new key is read.
1266
- # Fix: drain all immediately available bytes
1267
- # (non-blocking) and use the last one.
1268
- key_text = read_burst(0.0).decode("ascii", errors="replace")
1313
+ # --- Read key, draining buffered repeats ---
1314
+ # When a key is held, the terminal auto-repeats it into the
1315
+ # input buffer. Reading only one character leaves stale
1316
+ # repeats behind — when the user switches from one
1317
+ # movement key to another, the last buffered copy of the
1318
+ # old key fires one final move in the wrong direction
1319
+ # before the new key is read.
1320
+ # Fix: drain all immediately available bytes
1321
+ # (non-blocking) and use the last one.
1322
+ key_text = read_burst(0.0).decode("ascii", errors="replace")
1323
+ finally:
1324
+ input_active.clear()
1269
1325
  key = key_text[-1].lower() if key_text else ""
1270
1326
  if not key:
1271
1327
  continue
1272
1328
 
1273
1329
  # Exit
1274
1330
  if key == "\x1b" or key == "\x03":
1331
+ # The main loop owns cleanup from here; the watcher
1332
+ # must not fire a second stop/restore in parallel.
1333
+ stop_interrupt_watcher()
1275
1334
  if state["freedrive"]:
1276
1335
  try:
1277
1336
  robot.disable_freedrive()
@@ -1317,6 +1376,9 @@ def _teach_pendant(
1317
1376
  moved = True
1318
1377
  last_move_t = time.monotonic()
1319
1378
  except MotionError as e:
1379
+ logger.debug(
1380
+ "move key %r failed: %s\n%s", key, e, traceback.format_exc()
1381
+ )
1320
1382
  messages.append(f"Error: {e}")
1321
1383
 
1322
1384
  # --- Step size ---
@@ -1527,6 +1589,7 @@ def _teach_pendant(
1527
1589
  print(f"\n{red('Fault:')} {e}")
1528
1590
  finally:
1529
1591
  # Stop monitor and restore signal handler
1592
+ stop_interrupt_watcher()
1530
1593
  monitor.stop()
1531
1594
  if _old_sigalrm is not None:
1532
1595
  signal.signal(signal.SIGALRM, _old_sigalrm)
@@ -30,8 +30,9 @@ from __future__ import annotations
30
30
  import ctypes
31
31
  import os
32
32
  import sys
33
+ import threading
33
34
  import time
34
- from typing import Any
35
+ from typing import Any, Callable
35
36
 
36
37
  if sys.platform == "win32":
37
38
  import msvcrt
@@ -126,6 +127,112 @@ if sys.platform == "win32":
126
127
  pass
127
128
 
128
129
 
130
+ input_active = threading.Event()
131
+ """Set while CLI code is actively reading stdin (input-wait loops, text
132
+ prompts, menus). The Windows interrupt watcher stays out of the console
133
+ input queue while this is set, so it can never steal a keystroke from
134
+ the main reader. Callers set it around their read sections and clear it
135
+ afterwards."""
136
+
137
+
138
+ watcher_stop = threading.Event()
139
+ """Set when the CLI begins its own shutdown (normal exit, fault exit,
140
+ Ctrl+C in the main loop). The Windows interrupt watcher exits quietly
141
+ without firing, so it cannot race the main thread's cleanup (double
142
+ robot stop, double terminal restore)."""
143
+
144
+
145
+ def stop_interrupt_watcher() -> None:
146
+ """Tell the interrupt watcher to exit quietly. No-op on non-Windows."""
147
+ watcher_stop.set()
148
+
149
+
150
+ def start_interrupt_watcher(
151
+ on_fire: Callable[[str], None],
152
+ fault_reason: Callable[[], str | None],
153
+ ) -> threading.Thread | None:
154
+ """Start the Windows-only interrupt watcher. Returns None elsewhere.
155
+
156
+ On Windows, while the main thread is blocked in a long RTDE call
157
+ (not reading input) nothing can interrupt it: Ctrl+C is a raw
158
+ console byte that only the input loops read, and there is no
159
+ SIGALRM. This daemon thread covers that gap. It polls, only while
160
+ input_active is clear:
161
+
162
+ - fault_reason() for a connection/robot fault
163
+ - the console for a Ctrl+C (0x03) byte; any other byte is pushed
164
+ back with ungetch() so the main reader still sees it, and the
165
+ watcher backs off to avoid interfering with resumed input
166
+
167
+ When either fires it calls on_fire(reason) exactly once; the
168
+ caller should clean up (stop robot, restore terminal) and exit.
169
+ """
170
+ if sys.platform != "win32":
171
+ return None
172
+
173
+ # Only reachable on Windows (guard above), so this never runs on
174
+ # Linux. Local import because mypy prunes the top-level platform
175
+ # guard and cannot see the module inside this closure.
176
+ import msvcrt
177
+
178
+ def _where_blocked() -> None:
179
+ # Diagnostic: report where the main thread was stuck so the
180
+ # next occurrence can be diagnosed (usually a blocking socket
181
+ # read inside ur_rtde with no timeout).
182
+ frame = sys._current_frames().get(threading.main_thread().ident)
183
+ if frame is None:
184
+ return
185
+ code = frame.f_code
186
+ sys.stderr.write(
187
+ "[urkit] main thread blocked in "
188
+ f"{os.path.basename(code.co_filename)}:{frame.f_lineno} "
189
+ f"in {code.co_name}\n"
190
+ )
191
+ sys.stderr.flush()
192
+
193
+ def _run() -> None:
194
+ backoff = 0.0
195
+ while not watcher_stop.is_set():
196
+ if backoff > 0:
197
+ time.sleep(backoff)
198
+ backoff = 0.0
199
+ if input_active.is_set():
200
+ time.sleep(0.05)
201
+ continue
202
+ reason = fault_reason()
203
+ if reason is not None:
204
+ _where_blocked()
205
+ on_fire(reason)
206
+ return
207
+ try:
208
+ if not msvcrt.kbhit():
209
+ time.sleep(0.05)
210
+ continue
211
+ byte = msvcrt.getch()
212
+ except OSError:
213
+ # Console is closing (shutdown in progress). The watcher
214
+ # is best-effort; exit quietly instead of dying with a
215
+ # traceback that races the main thread's exit.
216
+ return
217
+ if byte == b"\x03":
218
+ _where_blocked()
219
+ on_fire("Interrupted.")
220
+ return
221
+ # Not Ctrl+C: give the byte back and back off so the main
222
+ # reader (resuming from a blocking call) processes it.
223
+ try:
224
+ msvcrt.ungetch(byte[0])
225
+ except OSError:
226
+ return
227
+ backoff = 0.2
228
+
229
+ watcher = threading.Thread(
230
+ target=_run, daemon=True, name="interrupt-watcher"
231
+ )
232
+ watcher.start()
233
+ return watcher
234
+
235
+
129
236
  def warn_if_windows() -> None:
130
237
  """Print a notice when the interactive CLI runs on Windows.
131
238
 
@@ -30,17 +30,26 @@ def _suppress_rtde_stderr() -> Iterator[None]:
30
30
  to raw stderr (fd 2) on every call when the script is stopped.
31
31
  This context manager suppresses those messages by redirecting fd 2
32
32
  around the call.
33
+
34
+ Diagnostic: set URKIT_RTDE_STDERR=<path> to capture the suppressed
35
+ output in a file (append mode) instead. ur_rtde move failures are
36
+ reported there and nowhere else, so this is the only way to see
37
+ why a move was rejected.
33
38
  """
34
- devnull = os.open(os.devnull, os.O_WRONLY)
39
+ capture_path = os.environ.get("URKIT_RTDE_STDERR")
40
+ if capture_path:
41
+ target_fd = os.open(capture_path, os.O_WRONLY | os.O_CREAT | os.O_APPEND)
42
+ else:
43
+ target_fd = os.open(os.devnull, os.O_WRONLY)
35
44
  old_stderr = os.dup(2)
36
45
  try:
37
- os.dup2(devnull, 2)
46
+ os.dup2(target_fd, 2)
38
47
  sys.stderr.flush()
39
48
  yield
40
49
  finally:
41
50
  os.dup2(old_stderr, 2)
42
51
  os.close(old_stderr)
43
- os.close(devnull)
52
+ os.close(target_fd)
44
53
 
45
54
  if TYPE_CHECKING:
46
55
  from rtde_control import RTDEControlInterface
@@ -151,7 +160,14 @@ class Motion:
151
160
  "movej: joints=%s, vel=%.3f, acc=%.3f, async=%s", joints, vel, acc, asynchronous
152
161
  )
153
162
  with _suppress_rtde_stderr():
154
- self._rtde_c.moveJ(joints, vel, acc, asynchronous=asynchronous)
163
+ ok = self._rtde_c.moveJ(joints, vel, acc, asynchronous=asynchronous)
164
+ if ok is False:
165
+ raise MotionError(
166
+ "moveJ rejected by the controller: the RTDE control script "
167
+ "stopped, the robot entered a stop state, or the command "
168
+ "timed out. Set URKIT_RTDE_STDERR=<file> to capture "
169
+ "ur_rtde's error output."
170
+ )
155
171
  except MotionError:
156
172
  raise
157
173
  except Exception as e:
@@ -197,7 +213,14 @@ class Motion:
197
213
  "movel: pose=%s, vel=%.3f, acc=%.3f, async=%s", pose, vel, acc, asynchronous
198
214
  )
199
215
  with _suppress_rtde_stderr():
200
- self._rtde_c.moveL(pose, vel, acc, asynchronous=asynchronous)
216
+ ok = self._rtde_c.moveL(pose, vel, acc, asynchronous=asynchronous)
217
+ if ok is False:
218
+ raise MotionError(
219
+ "moveL rejected by the controller: the RTDE control script "
220
+ "stopped, the robot entered a stop state, or the command "
221
+ "timed out. Set URKIT_RTDE_STDERR=<file> to capture "
222
+ "ur_rtde's error output."
223
+ )
201
224
  except MotionError:
202
225
  raise
203
226
  except Exception as e:
@@ -249,7 +272,14 @@ class Motion:
249
272
  current, target, frame.name, vel, acc,
250
273
  )
251
274
  with _suppress_rtde_stderr():
252
- self._rtde_c.moveL(target, vel, acc)
275
+ ok = self._rtde_c.moveL(target, vel, acc)
276
+ if ok is False:
277
+ raise MotionError(
278
+ "moveL rejected by the controller: the RTDE control script "
279
+ "stopped, the robot entered a stop state, or the command "
280
+ "timed out. Set URKIT_RTDE_STDERR=<file> to capture "
281
+ "ur_rtde's error output."
282
+ )
253
283
  except MotionError:
254
284
  raise
255
285
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes