urkit 0.3.26__tar.gz → 0.4.0__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.26 → urkit-0.4.0}/PKG-INFO +71 -56
  2. {urkit-0.3.26 → urkit-0.4.0}/README.md +70 -55
  3. {urkit-0.3.26 → urkit-0.4.0}/pyproject.toml +1 -1
  4. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/__init__.py +1 -1
  5. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/teach.py +146 -32
  6. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/motion.py +34 -14
  7. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/robot.py +232 -292
  8. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/telemetry.py +0 -16
  9. {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/PKG-INFO +71 -56
  10. {urkit-0.3.26 → urkit-0.4.0}/tests/test_gripper_presets.py +1 -1
  11. {urkit-0.3.26 → urkit-0.4.0}/tests/test_move_sequence.py +49 -38
  12. {urkit-0.3.26 → urkit-0.4.0}/tests/test_robot_integration.py +17 -23
  13. {urkit-0.3.26 → urkit-0.4.0}/setup.cfg +0 -0
  14. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/__main__.py +0 -0
  15. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/__init__.py +0 -0
  16. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/colors.py +0 -0
  17. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/connection_monitor.py +0 -0
  18. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/points.py +0 -0
  19. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/config.py +0 -0
  20. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/connection.py +0 -0
  21. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/exceptions.py +0 -0
  22. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/geometry.py +0 -0
  23. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/__init__.py +0 -0
  24. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/base.py +0 -0
  25. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/digital.py +0 -0
  26. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/presets.py +0 -0
  27. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/robotiq.py +0 -0
  28. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/robotiq_preamble.py +0 -0
  29. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/io.py +0 -0
  30. {urkit-0.3.26 → urkit-0.4.0}/src/urkit/points.py +0 -0
  31. {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/SOURCES.txt +0 -0
  32. {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/dependency_links.txt +0 -0
  33. {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/entry_points.txt +0 -0
  34. {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/requires.txt +0 -0
  35. {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/top_level.txt +0 -0
  36. {urkit-0.3.26 → urkit-0.4.0}/tests/test_exceptions.py +0 -0
  37. {urkit-0.3.26 → urkit-0.4.0}/tests/test_geometry.py +0 -0
  38. {urkit-0.3.26 → urkit-0.4.0}/tests/test_gripper.py +0 -0
  39. {urkit-0.3.26 → urkit-0.4.0}/tests/test_gripper_factory.py +0 -0
  40. {urkit-0.3.26 → urkit-0.4.0}/tests/test_points.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.3.26
3
+ Version: 0.4.0
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
@@ -28,9 +28,27 @@ Requires-Dist: rich>=15
28
28
 
29
29
  [![PyPI](https://img.shields.io/pypi/v/urkit.svg)](https://pypi.org/project/urkit/)
30
30
 
31
- **URKit** is a Python toolkit for [Universal Robots](https://www.universal-robots.com/) e-Series robots that makes the common stuff simple and gets out of the way for everything else.
31
+ **URKit** makes it easy to get a Universal Robots e-Series robot moving from Python.
32
32
 
33
- Built on [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/), it packages the operations you reach for most: connecting, moving to named points, gripper control, telemetry, and I/O, while exposing the raw RTDE interfaces for anything deeper.
33
+ ## What it is
34
+
35
+ A thin layer over [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/) that handles the common stuff: connecting, teaching named points, moving between them, gripper control, telemetry. Raw RTDE interfaces are exposed for anything deeper.
36
+
37
+ No .urp files, no Dashboard API, no extra programs to run on the robot. Just connect over the network and go. Handles power-on, brake release, and RTDE setup in the constructor so you don't have to.
38
+
39
+ Comes with an interactive teach pendant CLI for positioning the robot and saving waypoints, plus a Python API for scripting motion, gripper control, I/O, and telemetry.
40
+
41
+ ## When to use this
42
+
43
+ Projects where the robot is part of something bigger. Computer vision, machine learning, sensor fusion, data logging. If your project lives in Python, keep the robot control in Python too.
44
+
45
+ Built for labs and research setups where you need to get the robot moving fast and integrate it with other software. Not designed as a drop-in replacement for Polyscope in production cells, but perfectly capable for anything that runs from a PC.
46
+
47
+ ## How it works
48
+
49
+ Use the CLI to position the robot and save named waypoints. Then reference them by name in your code: move to points, apply offsets, run sequences. Points are stored in a local SQLite database, no robot-side setup needed.
50
+
51
+ The typical workflow: teach points with the pendant, write a few lines of Python to string them together, run it. Add vision, add sensors, add logic. The robot is just one component in your pipeline.
34
52
 
35
53
  ---
36
54
 
@@ -70,7 +88,7 @@ Built on [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/), it packages the op
70
88
  pip install -U urkit
71
89
  ```
72
90
 
73
- The `-U` (upgrade) flag ensures you always get the latest version — this project is in early development and changes frequently.
91
+ The `-U` (upgrade) flag ensures you always get the latest version. This project is in early development and changes frequently.
74
92
 
75
93
  Requires Python 3.8+ and a Universal Robots e-Series (UR3e to UR30).
76
94
 
@@ -309,7 +327,8 @@ All movement and orientation keys support **hold-to-repeat**.
309
327
  <td align="center" style="width:34%">
310
328
  <table>
311
329
  <tr><th>Key</th><th>Action</th></tr>
312
- <tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ+Rz)</td></tr>
330
+ <tr><td><code>F</code></td><td>Freedrive toggle (ALL ↔ XYZ)</td></tr>
331
+ <tr><td><code>3</code></td><td>Freedrive axis menu (toggle individual axes)</td></tr>
313
332
  <tr><td><code>M</code></td><td>Toggle frame (BASE / TOOL)</td></tr>
314
333
  <tr><td><code>N</code></td><td>Go To mode (Cartesian / Joint)</td></tr>
315
334
  <tr><td><code>T</code></td><td>Open TCP orient submenu (6 directions)</td></tr>
@@ -502,11 +521,11 @@ while robot.is_moving():
502
521
  time.sleep(0.01)
503
522
  ```
504
523
 
505
- **Teach pendant Go To** uses this pattern internally — Space cancels the move and returns to the menu.
524
+ **Teach pendant Go To** uses this pattern internally. Space cancels the move and returns to the menu.
506
525
 
507
526
  #### Pose Format
508
527
 
509
- A pose is `[x, y, z, rx, ry, rz]`: position in meters and orientation as a **rotation vector** (axis-angle in radians). This is not RPY (roll/pitch/yaw). The teach pendant displays RPY in degrees, which is a different representation. Values you see on the pendant won't match `get_tcp_pose()` directly.
528
+ A pose is `[x, y, z, rx, ry, rz]`: position in meters and orientation as a **rotation vector** (axis-angle in radians). This is not RPY (roll/pitch/yaw). The teach pendant displays RPY in degrees, which is a different representation. Values you see on the pendant won't match `get_current_point()` directly.
510
529
 
511
530
  #### Offsets
512
531
 
@@ -523,8 +542,8 @@ robot.move_to("pick", offset=[0, 0, 0.05, 0, 0.1, 0]) # full with rotation
523
542
  Get a pose without moving. Useful for logging, comparisons, or custom motion:
524
543
 
525
544
  ```python
526
- pose = robot.get_pose("pick")
527
- pose = robot.get_pose("pick", offset=[0, 0, 0.05, 0, 0, 0]) # with offset
545
+ point = robot.get_point("pick")
546
+ point = robot.get_point("pick", offset=[0, 0, 0.05, 0, 0, 0]) # with offset
528
547
  robot.move_to(pose) # move to the resolved pose later
529
548
  ```
530
549
 
@@ -540,11 +559,11 @@ robot.move_relative(delta_z=0.05) # 5cm along tool Z
540
559
  - **BASE** (default): delta relative to robot base
541
560
  - **TOOL**: delta relative to TCP orientation
542
561
 
543
- #### IK Reference (recommended)
562
+ #### IK Reference
544
563
 
545
564
  **Problem:** When the robot has multiple valid joint configurations to reach the same TCP pose (e.g., elbow up vs. elbow down), it can unexpectedly flip its posture between moves. This is called an **IK ambiguity** and it causes "weird" movements where the robot takes a strange path or flips its wrist.
546
565
 
547
- **Solution:** Set an **IK reference posture** — a saved point that defines your preferred arm configuration. The robot then stays close to that posture for all moves.
566
+ **Solution:** Set an **IK reference posture**, a saved point that defines your preferred arm configuration. The robot then stays close to that posture for all moves.
548
567
 
549
568
  ```python
550
569
  # 1. Put the robot in your preferred posture (e.g., "home")
@@ -552,7 +571,7 @@ robot.move_relative(delta_z=0.05) # 5cm along tool Z
552
571
  # 3. Set as IK reference:
553
572
  robot.ik_reference = "home"
554
573
 
555
- # Now all moves stay close to that posture — no elbow flipping
574
+ # Now all moves stay close to that posture, no elbow flipping
556
575
  robot.move_to("pick")
557
576
  robot.move_to("place")
558
577
  robot.move_relative(delta_z=-0.05)
@@ -565,7 +584,7 @@ robot_ip: 192.168.1.50
565
584
  ik_reference: home # prevents weird elbow/wrist flips
566
585
  ```
567
586
 
568
- **How it works:** The robot's inverse kinematics solver uses the reference posture as a bias (`qnear`). The TCP still reaches the exact same pose, but the arm configuration (elbow up/down, wrist orientation) stays consistent with your reference.
587
+ **How it works:** The robot's inverse kinematics solver uses the reference posture as a bias (`qnear`). The TCP still reaches the exact same pose, but the arm configuration (elbow up/down, wrist orientation) stays consistent with your reference. Under the hood, poses are resolved to joint angles and sent as `moveJ` instead of `moveL`.
569
588
 
570
589
  **Per-move override:**
571
590
 
@@ -575,11 +594,16 @@ robot.move_to("weird_pose", ik_reference=None) # one move without it
575
594
  robot.move_to("back", ik_reference="current") # use current joints
576
595
  ```
577
596
 
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.
597
+ **When to use it:**
598
+
599
+ - **Use it** for sequences of positional moves between waypoints: pick/place paths, assembly sequences, anything where the robot travels between distant points. It prevents elbow/wrist flipping.
600
+ - **Don't use it** for rotation-heavy movements (orientation adjustments, fine-tuning angles). The `qnear` seed can push joints into unexpected configs for pure rotations, and you lose the controller's native Cartesian trajectory planner (lookahead, smoothing). Set `ik_reference=None` for these moves.
601
+
602
+ Default is `None` (controller handles IK natively). Set it globally when most of your moves are positional, and override per-move when you need rotations.
579
603
 
580
604
  #### Point Management
581
605
 
582
- Points are stored in the active TCP frame, so they work with any tool — swap grippers and your saved points stay valid.
606
+ Points are stored in the active TCP frame, so they work with any tool. Swap grippers and your saved points stay valid.
583
607
 
584
608
  ```python
585
609
  robot.save_point("here")
@@ -598,42 +622,32 @@ robot.move_relative(delta_z=0.05, frame=MoveFrame.TOOL) # 5cm along tool Z
598
622
  robot.move_relative([0, 0.01, 0, 0, 0, 0]) # full 6-element delta
599
623
  ```
600
624
 
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.
625
+ 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
626
 
603
627
  #### Sequences
604
628
 
605
629
  ```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
630
+ # Both styles work:
631
+ robot.move_sequence("a", "b", "c") # variadic
632
+ robot.move_sequence(["a", "b", "c"]) # list
609
633
  ```
610
634
 
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
-
613
- #### Sequences
614
-
615
- ```python
616
- # Chain multiple moves into one call
617
- robot.move_sequence(["a", "b", "c"])
618
- ```
635
+ `move_sequence` builds a path from all targets and executes it in a single call with blending. Requires at least 2 targets. Two modes:
619
636
 
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:
637
+ **With `ik_reference`** (positional paths): 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. Executed as a single `moveJ(path)` call. Keeps the arm configuration consistent (no elbow flipping):
621
638
 
622
639
  ```python
623
640
  robot.ik_reference = "home"
624
- robot.move_sequence(["a", "b", "c"]) # chained IK, no elbow flipping
641
+ robot.move_sequence(["a", "b", "c"], blend_radius=0.02) # 2cm blend
625
642
  ```
626
643
 
627
- With **blend_radius**, the robot rounds corners instead of stopping at each waypoint:
644
+ **Without `ik_reference`** (Cartesian path): Builds a blended `moveL(path)`. The controller handles IK natively, better for rotation-heavy sequences where `ik_reference` causes unexpected joint behavior:
628
645
 
629
646
  ```python
630
- robot.move_sequence(
631
- ["a", "b", "c"],
632
- blend_radius=0.02, # 2cm blend between waypoints
633
- )
647
+ robot.move_sequence(["a", "b", "c"], blend_radius=0.02) # blended Cartesian path
634
648
  ```
635
649
 
636
- `move_sequence` requires at least 2 targets. Without `ik_reference`, it falls back to individual `moveL` calls (legacy behavior, no blending).
650
+ Before sending any move, it checks that each pose has a valid IK solution. Unreachable poses raise `MotionError`. If a named point doesn't exist, it raises `PointError` instead of silently falling back.
637
651
 
638
652
  #### Contact Detection
639
653
 
@@ -657,7 +671,7 @@ robot.move_until_contact(speed_z=-0.02, timeout=10.0, max_distance=0.2)
657
671
  for _ in range(3):
658
672
  if robot.move_until_contact(speed_z=-0.02, timeout=10.0, max_distance=0.2):
659
673
  break # contact detected
660
- # No contact — back off and retry
674
+ # No contact, back off and retry
661
675
  robot.move_relative(delta_z=0.01)
662
676
 
663
677
  # Manual zero (e.g. before custom force-based logic)
@@ -676,33 +690,36 @@ robot.move_velocity([0, 0, -0.02, 0, 0, 0], duration=1.0)
676
690
  from urkit import FreedriveMode
677
691
 
678
692
  robot.enable_freedrive() # all 6 axes free
679
- robot.enable_freedrive(FreedriveMode.XYZ) # linear axes + Rz rotation
680
- robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only
693
+ robot.enable_freedrive(FreedriveMode.XYZ) # linear axes only (X, Y, Z)
694
+ robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only (Roll, Pitch, Yaw)
695
+ robot.enable_freedrive([1, 1, 1, 1, 0, 0]) # custom: X+Y+Z+Roll+Pitch
681
696
  robot.disable_freedrive() # disable before sending motion commands
682
697
  robot.is_freedrive_active # check state
683
698
  ```
684
699
 
700
+ **Teach pendant:** Press `F` to toggle freedrive (ALL ↔ XYZ). Press `3` to open the axis selection menu arrow keys navigate, space toggles each axis on/off, enter applies.
701
+
685
702
  #### Speed Control
686
703
 
687
704
  ```python
688
705
  robot.stop() # stop current move immediately (stopL + stopJ)
689
706
  robot.speed_stop() # stop velocity-controlled motion (not E-stop)
690
- robot.set_speed_slider(0.5) # 50% velocity cap
691
- robot.get_speed_slider() # read current slider (0.0-1.0)
707
+ robot.speed_slider = 0.5 # 50% velocity cap
708
+ robot.speed_slider # read current slider (0.0-1.0)
692
709
  ```
693
710
 
694
711
  The speed slider controls the pendant's speed multiplier. It's global, persistent, and affects all motion commands.
695
712
 
696
713
  #### Changing Default Speed & Acceleration
697
714
 
698
- Override the constructor defaults at runtime — all subsequent moves pick up the new values:
715
+ Override the constructor defaults at runtime. All subsequent moves pick up the new values:
699
716
 
700
717
  ```python
701
- robot.set_speed(0.1) # slow for precision work
718
+ robot.default_vel = 0.1 # slow for precision work
702
719
  robot.move_to("insert")
703
- robot.set_speed(0.5) # back to normal
720
+ robot.default_vel = 0.5 # back to normal
704
721
 
705
- robot.set_acc(0.05) # gentle acceleration
722
+ robot.default_acc = 0.05 # gentle acceleration
706
723
 
707
724
  robot.default_vel # read current velocity (m/s)
708
725
  robot.default_acc # read current acceleration (m/s²)
@@ -717,16 +734,14 @@ joints = robot.inverse_kinematics([0.5, 0, 0.3, 0, 0, 0])
717
734
  ### Telemetry
718
735
 
719
736
  ```python
720
- pose = robot.get_tcp_pose() # [x, y, z, rx, ry, rz]
721
- joints = robot.get_joint_positions() # [j0..j5]
722
- force = robot.get_tcp_force() # [fx, fy, fz, mx, my, mz]
723
- mode = robot.get_robot_mode() # "REMOTE_CONTROL", "SERVOING", etc.
724
- payload = robot.get_payload() # kg
725
- robot.current_point() # {"pose": [...], "joints": [...]}
726
- robot.is_protective_stopped() # bool
727
- robot.is_emergency_stopped() # bool
728
- robot.is_remote_mode() # bool — check remote control state
729
- robot.get_polyscope_version() # e.g. "5.25.0" or None
737
+ pose = robot.get_current_point() # [x, y, z, rx, ry, rz]
738
+ pose = robot.get_current_point(offset_z=0.05) # with offset
739
+ joints = robot.get_joint_positions() # [j0..j5]
740
+ force = robot.get_tcp_force() # [fx, fy, fz, mx, my, mz]
741
+ mode = robot.get_robot_mode() # "REMOTE_CONTROL", "SERVOING", etc.
742
+ payload = robot.payload # kg
743
+ robot.is_protective_stopped() # bool
744
+ robot.is_emergency_stopped() # bool
730
745
  ```
731
746
 
732
747
  #### Arrival Detection
@@ -744,7 +759,7 @@ while robot.is_moving():
744
759
  while robot.is_moving(position_tolerance=0.001, orientation_tolerance=0.017):
745
760
  time.sleep(0.01)
746
761
 
747
- # Joint moves — tighter joint tolerance
762
+ # Joint moves, tighter joint tolerance
748
763
  while robot.is_moving(joint_tolerance=0.001):
749
764
  time.sleep(0.01)
750
765
  ```
@@ -825,7 +840,7 @@ The constructor handles all of this automatically. Use these methods when you ne
825
840
  TCP and payload can be set manually (gripper presets do this automatically):
826
841
 
827
842
  ```python
828
- robot.set_tcp_offset([0, 0, 0.15, 0, 0, 0])
843
+ robot.tcp_offset = [0, 0, 0.15, 0, 0, 0]
829
844
  robot.set_payload(1.5, [0, 0, 0.05]) # mass (kg), center of gravity [x, y, z]
830
845
  ```
831
846
 
@@ -2,9 +2,27 @@
2
2
 
3
3
  [![PyPI](https://img.shields.io/pypi/v/urkit.svg)](https://pypi.org/project/urkit/)
4
4
 
5
- **URKit** is a Python toolkit for [Universal Robots](https://www.universal-robots.com/) e-Series robots that makes the common stuff simple and gets out of the way for everything else.
5
+ **URKit** makes it easy to get a Universal Robots e-Series robot moving from Python.
6
6
 
7
- Built on [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/), it packages the operations you reach for most: connecting, moving to named points, gripper control, telemetry, and I/O, while exposing the raw RTDE interfaces for anything deeper.
7
+ ## What it is
8
+
9
+ A thin layer over [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/) that handles the common stuff: connecting, teaching named points, moving between them, gripper control, telemetry. Raw RTDE interfaces are exposed for anything deeper.
10
+
11
+ No .urp files, no Dashboard API, no extra programs to run on the robot. Just connect over the network and go. Handles power-on, brake release, and RTDE setup in the constructor so you don't have to.
12
+
13
+ Comes with an interactive teach pendant CLI for positioning the robot and saving waypoints, plus a Python API for scripting motion, gripper control, I/O, and telemetry.
14
+
15
+ ## When to use this
16
+
17
+ Projects where the robot is part of something bigger. Computer vision, machine learning, sensor fusion, data logging. If your project lives in Python, keep the robot control in Python too.
18
+
19
+ Built for labs and research setups where you need to get the robot moving fast and integrate it with other software. Not designed as a drop-in replacement for Polyscope in production cells, but perfectly capable for anything that runs from a PC.
20
+
21
+ ## How it works
22
+
23
+ Use the CLI to position the robot and save named waypoints. Then reference them by name in your code: move to points, apply offsets, run sequences. Points are stored in a local SQLite database, no robot-side setup needed.
24
+
25
+ The typical workflow: teach points with the pendant, write a few lines of Python to string them together, run it. Add vision, add sensors, add logic. The robot is just one component in your pipeline.
8
26
 
9
27
  ---
10
28
 
@@ -44,7 +62,7 @@ Built on [`ur_rtde`](https://sdurobotics.gitlab.io/ur_rtde/), it packages the op
44
62
  pip install -U urkit
45
63
  ```
46
64
 
47
- The `-U` (upgrade) flag ensures you always get the latest version — this project is in early development and changes frequently.
65
+ The `-U` (upgrade) flag ensures you always get the latest version. This project is in early development and changes frequently.
48
66
 
49
67
  Requires Python 3.8+ and a Universal Robots e-Series (UR3e to UR30).
50
68
 
@@ -283,7 +301,8 @@ All movement and orientation keys support **hold-to-repeat**.
283
301
  <td align="center" style="width:34%">
284
302
  <table>
285
303
  <tr><th>Key</th><th>Action</th></tr>
286
- <tr><td><code>F</code></td><td>Freedrive (OFF → ALL → XYZ+Rz)</td></tr>
304
+ <tr><td><code>F</code></td><td>Freedrive toggle (ALL ↔ XYZ)</td></tr>
305
+ <tr><td><code>3</code></td><td>Freedrive axis menu (toggle individual axes)</td></tr>
287
306
  <tr><td><code>M</code></td><td>Toggle frame (BASE / TOOL)</td></tr>
288
307
  <tr><td><code>N</code></td><td>Go To mode (Cartesian / Joint)</td></tr>
289
308
  <tr><td><code>T</code></td><td>Open TCP orient submenu (6 directions)</td></tr>
@@ -476,11 +495,11 @@ while robot.is_moving():
476
495
  time.sleep(0.01)
477
496
  ```
478
497
 
479
- **Teach pendant Go To** uses this pattern internally — Space cancels the move and returns to the menu.
498
+ **Teach pendant Go To** uses this pattern internally. Space cancels the move and returns to the menu.
480
499
 
481
500
  #### Pose Format
482
501
 
483
- A pose is `[x, y, z, rx, ry, rz]`: position in meters and orientation as a **rotation vector** (axis-angle in radians). This is not RPY (roll/pitch/yaw). The teach pendant displays RPY in degrees, which is a different representation. Values you see on the pendant won't match `get_tcp_pose()` directly.
502
+ A pose is `[x, y, z, rx, ry, rz]`: position in meters and orientation as a **rotation vector** (axis-angle in radians). This is not RPY (roll/pitch/yaw). The teach pendant displays RPY in degrees, which is a different representation. Values you see on the pendant won't match `get_current_point()` directly.
484
503
 
485
504
  #### Offsets
486
505
 
@@ -497,8 +516,8 @@ robot.move_to("pick", offset=[0, 0, 0.05, 0, 0.1, 0]) # full with rotation
497
516
  Get a pose without moving. Useful for logging, comparisons, or custom motion:
498
517
 
499
518
  ```python
500
- pose = robot.get_pose("pick")
501
- pose = robot.get_pose("pick", offset=[0, 0, 0.05, 0, 0, 0]) # with offset
519
+ point = robot.get_point("pick")
520
+ point = robot.get_point("pick", offset=[0, 0, 0.05, 0, 0, 0]) # with offset
502
521
  robot.move_to(pose) # move to the resolved pose later
503
522
  ```
504
523
 
@@ -514,11 +533,11 @@ robot.move_relative(delta_z=0.05) # 5cm along tool Z
514
533
  - **BASE** (default): delta relative to robot base
515
534
  - **TOOL**: delta relative to TCP orientation
516
535
 
517
- #### IK Reference (recommended)
536
+ #### IK Reference
518
537
 
519
538
  **Problem:** When the robot has multiple valid joint configurations to reach the same TCP pose (e.g., elbow up vs. elbow down), it can unexpectedly flip its posture between moves. This is called an **IK ambiguity** and it causes "weird" movements where the robot takes a strange path or flips its wrist.
520
539
 
521
- **Solution:** Set an **IK reference posture** — a saved point that defines your preferred arm configuration. The robot then stays close to that posture for all moves.
540
+ **Solution:** Set an **IK reference posture**, a saved point that defines your preferred arm configuration. The robot then stays close to that posture for all moves.
522
541
 
523
542
  ```python
524
543
  # 1. Put the robot in your preferred posture (e.g., "home")
@@ -526,7 +545,7 @@ robot.move_relative(delta_z=0.05) # 5cm along tool Z
526
545
  # 3. Set as IK reference:
527
546
  robot.ik_reference = "home"
528
547
 
529
- # Now all moves stay close to that posture — no elbow flipping
548
+ # Now all moves stay close to that posture, no elbow flipping
530
549
  robot.move_to("pick")
531
550
  robot.move_to("place")
532
551
  robot.move_relative(delta_z=-0.05)
@@ -539,7 +558,7 @@ robot_ip: 192.168.1.50
539
558
  ik_reference: home # prevents weird elbow/wrist flips
540
559
  ```
541
560
 
542
- **How it works:** The robot's inverse kinematics solver uses the reference posture as a bias (`qnear`). The TCP still reaches the exact same pose, but the arm configuration (elbow up/down, wrist orientation) stays consistent with your reference.
561
+ **How it works:** The robot's inverse kinematics solver uses the reference posture as a bias (`qnear`). The TCP still reaches the exact same pose, but the arm configuration (elbow up/down, wrist orientation) stays consistent with your reference. Under the hood, poses are resolved to joint angles and sent as `moveJ` instead of `moveL`.
543
562
 
544
563
  **Per-move override:**
545
564
 
@@ -549,11 +568,16 @@ robot.move_to("weird_pose", ik_reference=None) # one move without it
549
568
  robot.move_to("back", ik_reference="current") # use current joints
550
569
  ```
551
570
 
552
- **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.
571
+ **When to use it:**
572
+
573
+ - **Use it** for sequences of positional moves between waypoints: pick/place paths, assembly sequences, anything where the robot travels between distant points. It prevents elbow/wrist flipping.
574
+ - **Don't use it** for rotation-heavy movements (orientation adjustments, fine-tuning angles). The `qnear` seed can push joints into unexpected configs for pure rotations, and you lose the controller's native Cartesian trajectory planner (lookahead, smoothing). Set `ik_reference=None` for these moves.
575
+
576
+ Default is `None` (controller handles IK natively). Set it globally when most of your moves are positional, and override per-move when you need rotations.
553
577
 
554
578
  #### Point Management
555
579
 
556
- Points are stored in the active TCP frame, so they work with any tool — swap grippers and your saved points stay valid.
580
+ Points are stored in the active TCP frame, so they work with any tool. Swap grippers and your saved points stay valid.
557
581
 
558
582
  ```python
559
583
  robot.save_point("here")
@@ -572,42 +596,32 @@ robot.move_relative(delta_z=0.05, frame=MoveFrame.TOOL) # 5cm along tool Z
572
596
  robot.move_relative([0, 0.01, 0, 0, 0, 0]) # full 6-element delta
573
597
  ```
574
598
 
575
- 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.
599
+ 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.
576
600
 
577
601
  #### Sequences
578
602
 
579
603
  ```python
580
- robot.move_relative(delta_y=0.01) # 1cm along Y
581
- robot.move_relative(delta_z=0.05, frame=MoveFrame.TOOL) # 5cm along tool Z
582
- robot.move_relative([0, 0.01, 0, 0, 0, 0]) # full 6-element delta
604
+ # Both styles work:
605
+ robot.move_sequence("a", "b", "c") # variadic
606
+ robot.move_sequence(["a", "b", "c"]) # list
583
607
  ```
584
608
 
585
- 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.
586
-
587
- #### Sequences
588
-
589
- ```python
590
- # Chain multiple moves into one call
591
- robot.move_sequence(["a", "b", "c"])
592
- ```
609
+ `move_sequence` builds a path from all targets and executes it in a single call with blending. Requires at least 2 targets. Two modes:
593
610
 
594
- 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:
611
+ **With `ik_reference`** (positional paths): 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. Executed as a single `moveJ(path)` call. Keeps the arm configuration consistent (no elbow flipping):
595
612
 
596
613
  ```python
597
614
  robot.ik_reference = "home"
598
- robot.move_sequence(["a", "b", "c"]) # chained IK, no elbow flipping
615
+ robot.move_sequence(["a", "b", "c"], blend_radius=0.02) # 2cm blend
599
616
  ```
600
617
 
601
- With **blend_radius**, the robot rounds corners instead of stopping at each waypoint:
618
+ **Without `ik_reference`** (Cartesian path): Builds a blended `moveL(path)`. The controller handles IK natively, better for rotation-heavy sequences where `ik_reference` causes unexpected joint behavior:
602
619
 
603
620
  ```python
604
- robot.move_sequence(
605
- ["a", "b", "c"],
606
- blend_radius=0.02, # 2cm blend between waypoints
607
- )
621
+ robot.move_sequence(["a", "b", "c"], blend_radius=0.02) # blended Cartesian path
608
622
  ```
609
623
 
610
- `move_sequence` requires at least 2 targets. Without `ik_reference`, it falls back to individual `moveL` calls (legacy behavior, no blending).
624
+ Before sending any move, it checks that each pose has a valid IK solution. Unreachable poses raise `MotionError`. If a named point doesn't exist, it raises `PointError` instead of silently falling back.
611
625
 
612
626
  #### Contact Detection
613
627
 
@@ -631,7 +645,7 @@ robot.move_until_contact(speed_z=-0.02, timeout=10.0, max_distance=0.2)
631
645
  for _ in range(3):
632
646
  if robot.move_until_contact(speed_z=-0.02, timeout=10.0, max_distance=0.2):
633
647
  break # contact detected
634
- # No contact — back off and retry
648
+ # No contact, back off and retry
635
649
  robot.move_relative(delta_z=0.01)
636
650
 
637
651
  # Manual zero (e.g. before custom force-based logic)
@@ -650,33 +664,36 @@ robot.move_velocity([0, 0, -0.02, 0, 0, 0], duration=1.0)
650
664
  from urkit import FreedriveMode
651
665
 
652
666
  robot.enable_freedrive() # all 6 axes free
653
- robot.enable_freedrive(FreedriveMode.XYZ) # linear axes + Rz rotation
654
- robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only
667
+ robot.enable_freedrive(FreedriveMode.XYZ) # linear axes only (X, Y, Z)
668
+ robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only (Roll, Pitch, Yaw)
669
+ robot.enable_freedrive([1, 1, 1, 1, 0, 0]) # custom: X+Y+Z+Roll+Pitch
655
670
  robot.disable_freedrive() # disable before sending motion commands
656
671
  robot.is_freedrive_active # check state
657
672
  ```
658
673
 
674
+ **Teach pendant:** Press `F` to toggle freedrive (ALL ↔ XYZ). Press `3` to open the axis selection menu arrow keys navigate, space toggles each axis on/off, enter applies.
675
+
659
676
  #### Speed Control
660
677
 
661
678
  ```python
662
679
  robot.stop() # stop current move immediately (stopL + stopJ)
663
680
  robot.speed_stop() # stop velocity-controlled motion (not E-stop)
664
- robot.set_speed_slider(0.5) # 50% velocity cap
665
- robot.get_speed_slider() # read current slider (0.0-1.0)
681
+ robot.speed_slider = 0.5 # 50% velocity cap
682
+ robot.speed_slider # read current slider (0.0-1.0)
666
683
  ```
667
684
 
668
685
  The speed slider controls the pendant's speed multiplier. It's global, persistent, and affects all motion commands.
669
686
 
670
687
  #### Changing Default Speed & Acceleration
671
688
 
672
- Override the constructor defaults at runtime — all subsequent moves pick up the new values:
689
+ Override the constructor defaults at runtime. All subsequent moves pick up the new values:
673
690
 
674
691
  ```python
675
- robot.set_speed(0.1) # slow for precision work
692
+ robot.default_vel = 0.1 # slow for precision work
676
693
  robot.move_to("insert")
677
- robot.set_speed(0.5) # back to normal
694
+ robot.default_vel = 0.5 # back to normal
678
695
 
679
- robot.set_acc(0.05) # gentle acceleration
696
+ robot.default_acc = 0.05 # gentle acceleration
680
697
 
681
698
  robot.default_vel # read current velocity (m/s)
682
699
  robot.default_acc # read current acceleration (m/s²)
@@ -691,16 +708,14 @@ joints = robot.inverse_kinematics([0.5, 0, 0.3, 0, 0, 0])
691
708
  ### Telemetry
692
709
 
693
710
  ```python
694
- pose = robot.get_tcp_pose() # [x, y, z, rx, ry, rz]
695
- joints = robot.get_joint_positions() # [j0..j5]
696
- force = robot.get_tcp_force() # [fx, fy, fz, mx, my, mz]
697
- mode = robot.get_robot_mode() # "REMOTE_CONTROL", "SERVOING", etc.
698
- payload = robot.get_payload() # kg
699
- robot.current_point() # {"pose": [...], "joints": [...]}
700
- robot.is_protective_stopped() # bool
701
- robot.is_emergency_stopped() # bool
702
- robot.is_remote_mode() # bool — check remote control state
703
- robot.get_polyscope_version() # e.g. "5.25.0" or None
711
+ pose = robot.get_current_point() # [x, y, z, rx, ry, rz]
712
+ pose = robot.get_current_point(offset_z=0.05) # with offset
713
+ joints = robot.get_joint_positions() # [j0..j5]
714
+ force = robot.get_tcp_force() # [fx, fy, fz, mx, my, mz]
715
+ mode = robot.get_robot_mode() # "REMOTE_CONTROL", "SERVOING", etc.
716
+ payload = robot.payload # kg
717
+ robot.is_protective_stopped() # bool
718
+ robot.is_emergency_stopped() # bool
704
719
  ```
705
720
 
706
721
  #### Arrival Detection
@@ -718,7 +733,7 @@ while robot.is_moving():
718
733
  while robot.is_moving(position_tolerance=0.001, orientation_tolerance=0.017):
719
734
  time.sleep(0.01)
720
735
 
721
- # Joint moves — tighter joint tolerance
736
+ # Joint moves, tighter joint tolerance
722
737
  while robot.is_moving(joint_tolerance=0.001):
723
738
  time.sleep(0.01)
724
739
  ```
@@ -799,7 +814,7 @@ The constructor handles all of this automatically. Use these methods when you ne
799
814
  TCP and payload can be set manually (gripper presets do this automatically):
800
815
 
801
816
  ```python
802
- robot.set_tcp_offset([0, 0, 0.15, 0, 0, 0])
817
+ robot.tcp_offset = [0, 0, 0.15, 0, 0, 0]
803
818
  robot.set_payload(1.5, [0, 0, 0.05]) # mass (kg), center of gravity [x, y, z]
804
819
  ```
805
820
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "urkit"
7
- version = "0.3.26"
7
+ version = "0.4.0"
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.3.26"
27
+ __version__ = "0.4.0"
28
28
 
29
29
 
30
30
  from urkit.exceptions import (