urkit 0.3.24__tar.gz → 0.3.26__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 (40) hide show
  1. {urkit-0.3.24 → urkit-0.3.26}/PKG-INFO +251 -111
  2. {urkit-0.3.24 → urkit-0.3.26}/README.md +250 -110
  3. {urkit-0.3.24 → urkit-0.3.26}/pyproject.toml +1 -1
  4. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/__init__.py +3 -5
  5. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/cli/points.py +1 -1
  6. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/cli/teach.py +4 -6
  7. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/config.py +7 -12
  8. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/robot.py +2 -2
  9. {urkit-0.3.24 → urkit-0.3.26}/src/urkit.egg-info/PKG-INFO +251 -111
  10. {urkit-0.3.24 → urkit-0.3.26}/setup.cfg +0 -0
  11. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/__main__.py +0 -0
  12. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/cli/__init__.py +0 -0
  13. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/cli/colors.py +0 -0
  14. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/cli/connection_monitor.py +0 -0
  15. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/connection.py +0 -0
  16. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/exceptions.py +0 -0
  17. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/geometry.py +0 -0
  18. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/gripper/__init__.py +0 -0
  19. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/gripper/base.py +0 -0
  20. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/gripper/digital.py +0 -0
  21. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/gripper/presets.py +0 -0
  22. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/gripper/robotiq.py +0 -0
  23. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/gripper/robotiq_preamble.py +0 -0
  24. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/io.py +0 -0
  25. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/motion.py +0 -0
  26. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/points.py +0 -0
  27. {urkit-0.3.24 → urkit-0.3.26}/src/urkit/telemetry.py +0 -0
  28. {urkit-0.3.24 → urkit-0.3.26}/src/urkit.egg-info/SOURCES.txt +0 -0
  29. {urkit-0.3.24 → urkit-0.3.26}/src/urkit.egg-info/dependency_links.txt +0 -0
  30. {urkit-0.3.24 → urkit-0.3.26}/src/urkit.egg-info/entry_points.txt +0 -0
  31. {urkit-0.3.24 → urkit-0.3.26}/src/urkit.egg-info/requires.txt +0 -0
  32. {urkit-0.3.24 → urkit-0.3.26}/src/urkit.egg-info/top_level.txt +0 -0
  33. {urkit-0.3.24 → urkit-0.3.26}/tests/test_exceptions.py +0 -0
  34. {urkit-0.3.24 → urkit-0.3.26}/tests/test_geometry.py +0 -0
  35. {urkit-0.3.24 → urkit-0.3.26}/tests/test_gripper.py +0 -0
  36. {urkit-0.3.24 → urkit-0.3.26}/tests/test_gripper_factory.py +0 -0
  37. {urkit-0.3.24 → urkit-0.3.26}/tests/test_gripper_presets.py +0 -0
  38. {urkit-0.3.24 → urkit-0.3.26}/tests/test_move_sequence.py +0 -0
  39. {urkit-0.3.24 → urkit-0.3.26}/tests/test_points.py +0 -0
  40. {urkit-0.3.24 → urkit-0.3.26}/tests/test_robot_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.3.24
3
+ Version: 0.3.26
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
@@ -37,6 +37,12 @@ Built on [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/), it packages the op
37
37
  ## Table of Contents
38
38
 
39
39
  - [Quick Start](#quick-start)
40
+ - [Configuration](#configuration)
41
+ - [Location](#location)
42
+ - [Keys](#keys)
43
+ - [Gripper Config](#gripper-config)
44
+ - [Saving Config](#saving-config)
45
+ - [Programmatic](#programmatic)
40
46
  - [Interactive CLI](#interactive-cli)
41
47
  - [Teach Mode](#teach-mode)
42
48
  - [Points Explorer](#points-explorer)
@@ -46,8 +52,10 @@ Built on [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/), it packages the op
46
52
  - [Grippers](#grippers)
47
53
  - [Points & Motion](#points--motion)
48
54
  - [Telemetry](#telemetry)
55
+ - [More API](#more-api)
49
56
  - [Digital I/O](#digital-io)
50
- - [Configuration](#configuration)
57
+ - [Geometry](#geometry)
58
+ - [Robot Lifecycle](#robot-lifecycle)
51
59
  - [Advanced](#advanced)
52
60
  - [Raw RTDE Access](#raw-rtde-access)
53
61
  - [Connection Lifecycle](#connection-lifecycle)
@@ -102,6 +110,100 @@ The typical workflow:
102
110
 
103
111
  ---
104
112
 
113
+ ## Configuration
114
+
115
+ URKit uses a YAML config file (`config.yaml`) to persist settings between sessions.
116
+
117
+ ### Location
118
+
119
+ URKit searches for `config.yaml` in the current working directory, or an explicit path via `--config`.
120
+
121
+ ### Keys
122
+
123
+ | Key | Description | Example |
124
+ |-----|-------------|---------|
125
+ | `robot_ip` | Robot IP address | `192.168.1.50` |
126
+ | `points_path` | Path to SQLite points database | `points.db` |
127
+ | `gripper` | Gripper preset name | `hand-e`, `2f-85`, `2f-140`, `digital` |
128
+ | `ik_reference` | IK reference posture (prevents elbow flipping) | `home` |
129
+ | `default_vel` | Default linear velocity (m/s) | `0.5` |
130
+ | `default_acc` | Default linear acceleration (m/s²) | `0.3` |
131
+ | `expert_mode` | Disable safety speed clamping | `false` |
132
+
133
+ ### Gripper Config
134
+
135
+ Built-in preset with overrides:
136
+
137
+ ```yaml
138
+ gripper: hand-e
139
+ gripper_config:
140
+ force: 50
141
+ speed: 80
142
+ ```
143
+
144
+ Digital I/O gripper:
145
+
146
+ ```yaml
147
+ gripper: digital
148
+ gripper_config:
149
+ pin: 3
150
+ close_on_high: true
151
+ ```
152
+
153
+ Custom gripper (arbitrary payload + TCP offset, no backend):
154
+
155
+ ```yaml
156
+ gripper:
157
+ mass: 0.5
158
+ center_of_gravity: [0.0, 0.0, 0.0]
159
+ tcp_offset: [0.0, 0.0, 0.175, 0.0, 0.0, 0.0]
160
+ backend: none
161
+ ```
162
+
163
+ Override physical properties on a built-in preset:
164
+
165
+ ```yaml
166
+ gripper: 2f-85
167
+ gripper_config:
168
+ mass: 1.2
169
+ center_of_gravity: [0.0, 0.0, 0.07]
170
+ tcp_offset: [0.0, 0.0, 0.200, 0.0, 0.0, 0.0]
171
+ ```
172
+
173
+ ### CLI Override Precedence
174
+
175
+ 1. **CLI flags.** `urkit teach 192.168.1.50 --gripper none`
176
+ 2. **Config file.** Values from `config.yaml`
177
+ 3. **Built-in defaults.** `points.db`, no gripper, 0.5 m/s velocity
178
+
179
+ ### Saving Config
180
+
181
+ The CLI **never** modifies your config file automatically. Press **Y** inside the teach pendant to save. This way you only save settings you've actually tested.
182
+
183
+ ```bash
184
+ urkit teach 192.168.1.50 --gripper hand-e # test, then press Y
185
+ urkit teach # next time: reads from config
186
+ ```
187
+
188
+ Multiple workcells:
189
+
190
+ ```bash
191
+ urkit teach --config station_a.yaml # press Y to save
192
+ urkit teach --config station_b.yaml # separate config
193
+ ```
194
+
195
+ ### Programmatic
196
+
197
+ ```python
198
+ from urkit import URRobot
199
+
200
+ robot = URRobot.from_config("config.yaml")
201
+ robot = URRobot.from_config("config.yaml", ip="10.0.0.50") # override IP
202
+ robot = URRobot.from_config({"robot_ip": "192.168.1.50", "gripper": "2f-85"}) # dict, no file
203
+ ```
204
+
205
+ ---
206
+
105
207
  ## Interactive CLI
106
208
 
107
209
  URKit provides two CLI tools: **teach** for interactive robot control, and **points** for browsing saved waypoints.
@@ -192,7 +294,6 @@ All movement and orientation keys support **hold-to-repeat**.
192
294
  <tr><td><code>V</code></td><td>Set position (mm)</td></tr>
193
295
  <tr><td><code>6</code></td><td>Set speed (0-100)</td></tr>
194
296
  <tr><td><code>7</code></td><td>Set force (0-100)</td></tr>
195
- <tr><td colspan="3">Gripper line shows: `Connected 25.0mm (50%) F=100 S=100`</td></tr>
196
297
  </table>
197
298
  </td>
198
299
  <td align="center" style="width:33%">
@@ -211,7 +312,7 @@ All movement and orientation keys support **hold-to-repeat**.
211
312
  <tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ+Rz)</td></tr>
212
313
  <tr><td><code>M</code></td><td>Toggle frame (BASE / TOOL)</td></tr>
213
314
  <tr><td><code>N</code></td><td>Go To mode (Cartesian / Joint)</td></tr>
214
- <tr><td><code>T</code></td><td>Orient TCP down (180°)</td></tr>
315
+ <tr><td><code>T</code></td><td>Open TCP orient submenu (6 directions)</td></tr>
215
316
  <tr><td><code>Y</code></td><td>Save config to file</td></tr>
216
317
  <tr><td><code>ESC</code></td><td>Exit</td></tr>
217
318
  </table>
@@ -224,10 +325,10 @@ All movement and orientation keys support **hold-to-repeat**.
224
325
  The teach pendant shows live joint angles alongside TCP position and orientation:
225
326
 
226
327
  ```
227
- Position X=+0.432 Y=+0.111 Z=+0.227
228
- Orientation R=+131.3 P=-121.0 Y= +8.0
229
- Joints J1=+150.0 J2=+020.0 J3=+160.0
230
- J4=+050.0 J5=-080.0 J6=+157.0
328
+ Position X=+0.432 Y=+0.111 Z=+0.227
329
+ Orientation R=+131.3 P=-121.0 Y= +8.0
330
+ Joints J1=+150.0 J2=+ 20.0 J3=+160.0
331
+ J4=+ 50.0 J5=- 80.0 J6=+157.0
231
332
  ```
232
333
 
233
334
  Joint angles color-code proximity to mechanical limits:
@@ -237,20 +338,20 @@ Joint angles color-code proximity to mechanical limits:
237
338
 
238
339
  UR e-Series joint limits:
239
340
 
240
- | Joint | Range | Notes |
241
- |-------|-------|-------|
242
- | J1 (shoulder pan) | ±360° | Full rotation |
243
- | J2 (shoulder lift) | ±360° | Full rotation |
244
- | J3 (elbow) | ±180° | Physically restricted — shoulder lift gets in the way |
245
- | J4 (wrist 1) | ±360° | Full rotation |
246
- | J5 (wrist 2) | ±360° | Full rotation |
247
- | J6 (wrist 3) | ±360° | Tool flange unlimited rotation |
341
+ | Joint | Range |
342
+ |-------|-------|
343
+ | J1 (shoulder pan) | ±360° |
344
+ | J2 (shoulder lift) | ±360° |
345
+ | J3 (elbow) | ±360° |
346
+ | J4 (wrist 1) | ±360° |
347
+ | J5 (wrist 2) | ±360° |
348
+ | J6 (wrist 3) | ±360° |
349
+
248
350
 
249
- Thresholds scale with each joint's range, so warning zones feel proportional across all joints.
250
351
 
251
352
  ### Safety
252
353
 
253
- By default, **Go To** and **TCP Down** movements use a slow velocity (0.125 m/s) so its safer for anyone standing near the robot. The user's speed slider still applies as a global multiplier on top of this.
354
+ By default, **Go To** and **TCP orient** movements use a slow velocity (0.125 m/s) so its safer for anyone standing near the robot. The user's speed slider still applies as a global multiplier on top of this.
254
355
 
255
356
  Delta movements (W/S/A/D/Q/E) use step-size-based velocities that scale with the speed slider set by the user.
256
357
 
@@ -289,12 +390,9 @@ robot = URRobot(
289
390
  )
290
391
  ```
291
392
 
292
- From a config file:
393
+ See [Configuration](#configuration) for `from_config()` usage.
293
394
 
294
- ```python
295
- robot = URRobot.from_config("config.yaml")
296
- robot = URRobot.from_config("config.yaml", ip="10.0.0.50") # override IP
297
- ```
395
+ The constructor takes a few seconds on first call: it validates the connection, checks remote mode, powers on the robot, releases brakes, and connects RTDE. Subsequent calls are faster if the robot is already running.
298
396
 
299
397
  ### Grippers
300
398
 
@@ -302,21 +400,25 @@ Three built-in presets:
302
400
 
303
401
  | Preset | Description |
304
402
  |--------|-------------|
305
- | `ROBOTIQ_HAND_E` | Robotiq 2F-140-E (Hand-E series) |
403
+ | `ROBOTIQ_HAND_E` | Robotiq Hand-E (2F-85-E) |
306
404
  | `ROBOTIQ_2F_85` | Robotiq 2F-85 |
307
405
  | `ROBOTIQ_2F_140` | Robotiq 2F-140 |
308
406
 
309
407
  ```python
310
408
  robot.gripper.activate() # required before open/close (Robotiq only)
409
+ robot.gripper.deactivate() # deactivate (Robotiq only)
311
410
  robot.gripper.is_activated() # check activation state
312
411
 
313
412
  robot.gripper.open() # fully open (blocking by default)
314
413
  robot.gripper.close() # fully closed, stops on contact
315
- robot.gripper.open(wait=False) # non-blocking return
316
- robot.gripper.set_position_mm(20) # 20mm open (Robotiq only, 0 = closed)
317
- robot.gripper.set_position_percent(50) # 50% open (Robotiq only, 0 = open, 100 = closed)
414
+ robot.gripper.open(wait=False) # non-blocking return (waits for position)
415
+ robot.gripper.set_position_mm(20) # 20mm open (Robotiq only, 0 = fully closed)
416
+ robot.gripper.set_position_percent(50) # 50% open (Robotiq only, 0 = fully open, 100 = fully closed)
318
417
  robot.gripper.set_force(50) # grip force: 0-100 (Robotiq only)
319
418
  robot.gripper.set_speed(80) # movement speed: 0-100 (Robotiq only)
419
+
420
+ robot.gripper.get_position_mm() # last commanded position in mm
421
+ robot.gripper.max_travel_mm() # max finger travel (e.g. 85.0 for 2F-85)
320
422
  ```
321
423
 
322
424
  Override preset values for custom fingers:
@@ -325,6 +427,19 @@ Override preset values for custom fingers:
325
427
  robot = URRobot(ip="192.168.1.50", points="points.db", gripper=ROBOTIQ_HAND_E, max_mm=120)
326
428
  ```
327
429
 
430
+ Override physical properties (e.g., custom fingers or added hardware change the weight):
431
+
432
+ ```python
433
+ robot = URRobot(
434
+ ip="192.168.1.50",
435
+ points="points.db",
436
+ gripper=ROBOTIQ_HAND_E,
437
+ mass=1.2, # override preset mass
438
+ center_of_gravity=[0.0, 0.0, 0.07], # override CoG
439
+ tcp_offset=[0.0, 0.0, 0.180, 0, 0, 0], # override TCP offset
440
+ )
441
+ ```
442
+
328
443
  #### Digital I/O Grippers
329
444
 
330
445
  Robotiq grippers use a serial protocol over the robot's RS485 port. If you have a suction cup, solenoid, or any actuator controlled by a single digital output pin, use `DigitalGripperConfig` instead. It just turns that pin on (close) and off (open).
@@ -360,6 +475,7 @@ robot.move_to("pick") # linear move (default)
360
475
  robot.move_to("pick", linear=False) # joint move
361
476
  robot.move_to("pick", vel=1.0, acc=0.5) # override speed
362
477
  robot.move_to("pick", asynchronous=True) # non-blocking, returns immediately
478
+ robot.move_to([0.5, 0, 0.3, 0, 0, 0]) # raw pose (no points DB needed)
363
479
  ```
364
480
 
365
481
  - **Linear (moveL):** TCP moves in a straight line. Predictable path, slower near complex orientations.
@@ -377,11 +493,11 @@ robot.move_to("pick", asynchronous=True)
377
493
  while robot.is_moving():
378
494
  time.sleep(0.01)
379
495
 
380
- # Or cancel mid-move
496
+ # Or cancel mid-move (see [Speed Control](#speed-control) for `stop()`)
381
497
  robot.move_to("pick", asynchronous=True)
382
498
  while robot.is_moving():
383
499
  if should_cancel:
384
- robot.stop() # sends stopL + stopJ
500
+ robot.stop()
385
501
  break
386
502
  time.sleep(0.01)
387
503
  ```
@@ -461,12 +577,10 @@ robot.move_to("back", ik_reference="current") # use current joints
461
577
 
462
578
  **When to use it:** Almost always. If you've ever seen the robot move in a way that looked "wrong" or flipped its elbow unexpectedly, this is what fixes it. Set it once in config.yaml and forget about it.
463
579
 
464
- #### Points are tool-agnostic
465
-
466
- Points are stored in the active TCP frame, so they work with any tool. If you swap grippers and set the correct TCP offset, your saved points remain valid.
467
-
468
580
  #### Point Management
469
581
 
582
+ Points are stored in the active TCP frame, so they work with any tool — swap grippers and your saved points stay valid.
583
+
470
584
  ```python
471
585
  robot.save_point("here")
472
586
  robot.point_names() # ["home", "pick", "place"]
@@ -481,9 +595,21 @@ robot.import_points("backup.json")
481
595
  ```python
482
596
  robot.move_relative(delta_y=0.01) # 1cm along Y
483
597
  robot.move_relative(delta_z=0.05, frame=MoveFrame.TOOL) # 5cm along tool Z
484
- robot.move_relative([0, 0.01, 0, 0, 0, 0])
598
+ robot.move_relative([0, 0.01, 0, 0, 0, 0]) # full 6-element delta
599
+ ```
600
+
601
+ Individual delta parameters (`delta_x`, `delta_y`, `delta_z`, `delta_rx`, `delta_ry`, `delta_rz`) are mutually exclusive with the `delta` list — use one or the other.
602
+
603
+ #### Sequences
604
+
605
+ ```python
606
+ robot.move_relative(delta_y=0.01) # 1cm along Y
607
+ robot.move_relative(delta_z=0.05, frame=MoveFrame.TOOL) # 5cm along tool Z
608
+ robot.move_relative([0, 0.01, 0, 0, 0, 0]) # full 6-element delta
485
609
  ```
486
610
 
611
+ Individual delta parameters (`delta_x`, `delta_y`, `delta_z`, `delta_rx`, `delta_ry`, `delta_rz`) are mutually exclusive with the `delta` list — use one or the other.
612
+
487
613
  #### Sequences
488
614
 
489
615
  ```python
@@ -491,6 +617,24 @@ robot.move_relative([0, 0.01, 0, 0, 0, 0])
491
617
  robot.move_sequence(["a", "b", "c"])
492
618
  ```
493
619
 
620
+ With **IK reference** (recommended), all poses resolve to joints using chained inverse kinematics — the first pose resolves relative to the reference, the second relative to the first's resolved joints, and so on. This keeps the arm configuration consistent throughout the sequence:
621
+
622
+ ```python
623
+ robot.ik_reference = "home"
624
+ robot.move_sequence(["a", "b", "c"]) # chained IK, no elbow flipping
625
+ ```
626
+
627
+ With **blend_radius**, the robot rounds corners instead of stopping at each waypoint:
628
+
629
+ ```python
630
+ robot.move_sequence(
631
+ ["a", "b", "c"],
632
+ blend_radius=0.02, # 2cm blend between waypoints
633
+ )
634
+ ```
635
+
636
+ `move_sequence` requires at least 2 targets. Without `ik_reference`, it falls back to individual `moveL` calls (legacy behavior, no blending).
637
+
494
638
  #### Contact Detection
495
639
 
496
640
  ```python
@@ -514,7 +658,7 @@ for _ in range(3):
514
658
  if robot.move_until_contact(speed_z=-0.02, timeout=10.0, max_distance=0.2):
515
659
  break # contact detected
516
660
  # No contact — back off and retry
517
- robot.move_by(z=0.01)
661
+ robot.move_relative(delta_z=0.01)
518
662
 
519
663
  # Manual zero (e.g. before custom force-based logic)
520
664
  robot.zero_ft_sensor()
@@ -564,8 +708,6 @@ robot.default_vel # read current velocity (m/s)
564
708
  robot.default_acc # read current acceleration (m/s²)
565
709
  ```
566
710
 
567
- Soft warnings log when values exceed typical robot limits (> 2 m/s for velocity, > 6 m/s² for acceleration). The controller may clamp aggressive values based on payload and configuration.
568
-
569
711
  #### Inverse Kinematics
570
712
 
571
713
  ```python
@@ -580,116 +722,121 @@ joints = robot.get_joint_positions() # [j0..j5]
580
722
  force = robot.get_tcp_force() # [fx, fy, fz, mx, my, mz]
581
723
  mode = robot.get_robot_mode() # "REMOTE_CONTROL", "SERVOING", etc.
582
724
  payload = robot.get_payload() # kg
583
- robot.is_moving() # bool — any joint/TCP velocity non-zero
725
+ robot.current_point() # {"pose": [...], "joints": [...]}
584
726
  robot.is_protective_stopped() # bool
585
727
  robot.is_emergency_stopped() # bool
586
- robot.current_point() # {"pose": [...], "joints": [...]}
587
- robot.is_at_pose(target) # bool — TCP within 1mm / 0.5°
588
- robot.is_at_joints(target) # bool — all joints within 0.001 rad
728
+ robot.is_remote_mode() # bool — check remote control state
729
+ robot.get_polyscope_version() # e.g. "5.25.0" or None
589
730
  ```
590
731
 
591
- `is_moving()` is the simplest way to poll — `is_at_pose()` / `is_at_joints()` for precise target checking:
732
+ #### Arrival Detection
733
+
734
+ `is_moving()` tracks the target pose or joints stored by `move_to()` and compares against the current position. Returns `False` when within tolerance:
592
735
 
593
736
  ```python
594
737
  robot.move_to("pick", asynchronous=True)
595
738
 
596
- # Simple: wait until robot stops
739
+ # Wait until arrived (default tolerances: 2mm position, ~2° orientation)
597
740
  while robot.is_moving():
598
741
  time.sleep(0.01)
599
742
 
600
- # Precise: check against specific target
601
- while not robot.is_at_pose(target_pose):
743
+ # Tighter tolerances
744
+ while robot.is_moving(position_tolerance=0.001, orientation_tolerance=0.017):
745
+ time.sleep(0.01)
746
+
747
+ # Joint moves — tighter joint tolerance
748
+ while robot.is_moving(joint_tolerance=0.001):
602
749
  time.sleep(0.01)
603
750
  ```
604
751
 
752
+ For joint-space moves (when `ik_reference` is active or `linear=False`), `is_moving()` compares joint angles. For Cartesian moves, it compares TCP pose. When no target is set (e.g., after a relative move without `move_to`), it returns `False`.
753
+
754
+ ---
755
+
756
+ ## More API
757
+
758
+ Less common but useful when you need them.
759
+
605
760
  ### Digital I/O
606
761
 
762
+ Pins 0–7 are standard, 8–15 configurable, 16–17 tool.
763
+
607
764
  ```python
765
+ # Outputs
608
766
  robot.set_digital_output(0, True)
609
767
  robot.set_digital_outputs({0: True, 1: False, 8: True})
610
- robot.set_digital_outputs(False) # clear all
768
+ robot.set_digital_outputs(False) # clear all pins 0–15
769
+ robot.get_digital_output(0) # read back output state
611
770
 
771
+ # Inputs
612
772
  robot.get_digital_input(0)
613
- robot.get_analog_input(0)
614
- robot.get_tool_input(0)
615
-
616
773
  robot.wait_for_input(0, True, timeout=10.0) # block until pin 0 goes high
617
- ```
618
-
619
- ---
620
774
 
621
- ## Configuration
775
+ # Analog
776
+ robot.get_analog_input(0) # read analog input (pin 0–1)
777
+ robot.get_analog_output(0) # read analog output (pin 0–1)
622
778
 
623
- URKit uses a YAML config file (`config.yaml`) to persist settings between sessions.
779
+ # Tool I/O
780
+ robot.get_tool_input(0) # tool digital input (pin 0–1)
781
+ robot.get_tool_output(0) # tool digital output (pin 0–1)
782
+ ```
624
783
 
625
- ### Location
784
+ ### Geometry
626
785
 
627
- URKit searches for `config.yaml` in this order:
628
- 1. Explicit path via `--config` flag or `load_config("path")`
629
- 2. Project root (where `src/urkit` lives)
630
- 3. Current working directory
786
+ Conversion utilities for rotation vectors, quaternions, and RPY angles (rxyz convention, same as UR teach pendant):
631
787
 
632
- ### Keys
633
-
634
- | Key | Description | Example |
635
- |-----|-------------|---------|
636
- | `robot_ip` | Robot IP address | `192.168.1.50` |
637
- | `points_path` | Path to SQLite points database | `points.db` |
638
- | `gripper` | Gripper preset name | `hand-e`, `2f-85`, `2f-140`, `digital` |
639
- | `ik_reference` | IK reference posture (prevents elbow flipping) | `home` |
640
- | `default_vel` | Default linear velocity (m/s) | `0.5` |
641
- | `default_acc` | Default linear acceleration (m/s²) | `0.3` |
642
- | `expert_mode` | Disable safety speed clamping | `false` |
788
+ ```python
789
+ from urkit import (
790
+ orient_tcp,
791
+ orient_tcp_down,
792
+ quat_to_rotvec,
793
+ quat_to_rpy,
794
+ rpy_to_quat,
795
+ rotvec_to_quat,
796
+ )
643
797
 
644
- ### Gripper Config
798
+ # Orient TCP along an arbitrary direction (minimal rotation)
799
+ new_pose = orient_tcp(current_pose, [0, 0, -1]) # point down
800
+ new_pose = orient_tcp(current_pose, [1, 0, 0]) # point forward
645
801
 
646
- ```yaml
647
- gripper: digital
648
- gripper_config:
649
- pin: 3
650
- close_on_high: true
651
- ```
802
+ # Orient TCP straight down (convenience wrapper)
803
+ new_pose = orient_tcp_down(current_pose)
652
804
 
653
- ```yaml
654
- gripper: hand-e
655
- gripper_config:
656
- force: 50
657
- speed: 80
805
+ # Rotation conversions
806
+ q = rotvec_to_quat([0, 0, 1.57]) # rotvec → quaternion (x, y, z, w)
807
+ rv = quat_to_rotvec(q) # quaternion → rotvec
808
+ rpy = quat_to_rpy(q) # quaternion → RPY (radians)
809
+ q = rpy_to_quat(0, 0, 1.57) # RPY → quaternion
658
810
  ```
659
811
 
660
- ### CLI Override Precedence
812
+ ### Robot Lifecycle
661
813
 
662
- 1. **CLI flags.** `urkit teach 192.168.1.50 --gripper none`
663
- 2. **Config file.** Values from `config.yaml`
664
- 3. **Built-in defaults.** `points.db`, no gripper, 0.5 m/s velocity
665
-
666
- ### Saving Config
667
-
668
- The CLI **never** modifies your config file automatically. Press **Y** inside the teach pendant to save. This way you only save settings you've actually tested.
814
+ Manual power and brake control via the Dashboard:
669
815
 
670
- ```bash
671
- urkit teach 192.168.1.50 --gripper hand-e # test, then press Y
672
- urkit teach # next time: reads from config
816
+ ```python
817
+ robot.power_on() # power on (skips if already on)
818
+ robot.release_brakes() # release brakes (enable control)
819
+ robot.recover() # clear protective stop
820
+ robot.power_off() # power off
673
821
  ```
674
822
 
675
- Multiple workcells:
823
+ The constructor handles all of this automatically. Use these methods when you need fine-grained control (e.g., recovering from a safety stop without recreating the robot).
676
824
 
677
- ```bash
678
- urkit teach --config station_a.yaml # press Y to save
679
- urkit teach --config station_b.yaml # separate config
825
+ TCP and payload can be set manually (gripper presets do this automatically):
826
+
827
+ ```python
828
+ robot.set_tcp_offset([0, 0, 0.15, 0, 0, 0])
829
+ robot.set_payload(1.5, [0, 0, 0.05]) # mass (kg), center of gravity [x, y, z]
680
830
  ```
681
831
 
682
- ### Programmatic
832
+ Clean shutdown:
683
833
 
684
834
  ```python
685
- from urkit import load_config, resolve_config
686
-
687
- config = load_config() # auto-resolve
688
- config = load_config("/path/to/my.yaml") # explicit path
689
- path = resolve_config() # returns Path or None
690
- robot = URRobot.from_config({"robot_ip": "192.168.1.50", "gripper": "2f-85"})
835
+ robot.disconnect() # close RTDE, Dashboard, points DB, gripper
691
836
  ```
692
837
 
838
+ `disconnect()` is called automatically on garbage collection. Call it explicitly to release resources immediately.
839
+
693
840
  ---
694
841
 
695
842
  ## Advanced
@@ -713,8 +860,6 @@ robot.connection_lost # bool: check if RTDE dropped
713
860
  robot.reconnect_rtde() # reconnect after a drop
714
861
  ```
715
862
 
716
- `disconnect()` is called automatically when the robot object is garbage collected.
717
-
718
863
  ### Error Handling
719
864
 
720
865
  ```python
@@ -742,13 +887,8 @@ Common runtime errors:
742
887
 
743
888
  When the robot enters protective stop or the RTDE connection drops, motion commands raise `URKitConnectionError` and the program should exit. The CLI handles this automatically.
744
889
 
745
- ### Connection Notes
746
-
747
- The `URRobot` constructor takes a few seconds on first call: it validates the connection, checks remote mode, powers on the robot, releases brakes, and connects RTDE. Subsequent calls are faster if the robot is already running.
748
-
749
890
  ---
750
891
 
751
892
  ## Changelog
752
893
 
753
894
  See [CHANGELOG.md](CHANGELOG.md) for the full version history.
754
-