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.
- {urkit-0.3.26 → urkit-0.4.0}/PKG-INFO +71 -56
- {urkit-0.3.26 → urkit-0.4.0}/README.md +70 -55
- {urkit-0.3.26 → urkit-0.4.0}/pyproject.toml +1 -1
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/__init__.py +1 -1
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/teach.py +146 -32
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/motion.py +34 -14
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/robot.py +232 -292
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/telemetry.py +0 -16
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/PKG-INFO +71 -56
- {urkit-0.3.26 → urkit-0.4.0}/tests/test_gripper_presets.py +1 -1
- {urkit-0.3.26 → urkit-0.4.0}/tests/test_move_sequence.py +49 -38
- {urkit-0.3.26 → urkit-0.4.0}/tests/test_robot_integration.py +17 -23
- {urkit-0.3.26 → urkit-0.4.0}/setup.cfg +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/__main__.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/__init__.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/colors.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/connection_monitor.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/cli/points.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/config.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/connection.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/exceptions.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/geometry.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/__init__.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/base.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/digital.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/presets.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/robotiq.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/gripper/robotiq_preamble.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/io.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit/points.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/SOURCES.txt +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/dependency_links.txt +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/entry_points.txt +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/requires.txt +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/src/urkit.egg-info/top_level.txt +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/tests/test_exceptions.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/tests/test_geometry.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/tests/test_gripper.py +0 -0
- {urkit-0.3.26 → urkit-0.4.0}/tests/test_gripper_factory.py +0 -0
- {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
|
+
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
|
[](https://pypi.org/project/urkit/)
|
|
30
30
|
|
|
31
|
-
**URKit**
|
|
31
|
+
**URKit** makes it easy to get a Universal Robots e-Series robot moving from Python.
|
|
32
32
|
|
|
33
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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 `
|
|
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
|
-
|
|
527
|
-
|
|
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
|
|
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
|
|
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
|
|
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:**
|
|
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
|
|
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
|
|
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
|
-
|
|
607
|
-
robot.
|
|
608
|
-
robot.
|
|
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
|
-
|
|
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
|
|
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"]) #
|
|
641
|
+
robot.move_sequence(["a", "b", "c"], blend_radius=0.02) # 2cm blend
|
|
625
642
|
```
|
|
626
643
|
|
|
627
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
691
|
-
robot.
|
|
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
|
|
715
|
+
Override the constructor defaults at runtime. All subsequent moves pick up the new values:
|
|
699
716
|
|
|
700
717
|
```python
|
|
701
|
-
robot.
|
|
718
|
+
robot.default_vel = 0.1 # slow for precision work
|
|
702
719
|
robot.move_to("insert")
|
|
703
|
-
robot.
|
|
720
|
+
robot.default_vel = 0.5 # back to normal
|
|
704
721
|
|
|
705
|
-
robot.
|
|
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.
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
robot.
|
|
726
|
-
robot.is_protective_stopped()
|
|
727
|
-
robot.is_emergency_stopped()
|
|
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
|
|
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.
|
|
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
|
[](https://pypi.org/project/urkit/)
|
|
4
4
|
|
|
5
|
-
**URKit**
|
|
5
|
+
**URKit** makes it easy to get a Universal Robots e-Series robot moving from Python.
|
|
6
6
|
|
|
7
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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 `
|
|
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
|
-
|
|
501
|
-
|
|
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
|
|
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
|
|
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
|
|
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:**
|
|
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
|
|
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
|
|
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
|
-
|
|
581
|
-
robot.
|
|
582
|
-
robot.
|
|
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
|
-
|
|
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
|
|
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"]) #
|
|
615
|
+
robot.move_sequence(["a", "b", "c"], blend_radius=0.02) # 2cm blend
|
|
599
616
|
```
|
|
600
617
|
|
|
601
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
665
|
-
robot.
|
|
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
|
|
689
|
+
Override the constructor defaults at runtime. All subsequent moves pick up the new values:
|
|
673
690
|
|
|
674
691
|
```python
|
|
675
|
-
robot.
|
|
692
|
+
robot.default_vel = 0.1 # slow for precision work
|
|
676
693
|
robot.move_to("insert")
|
|
677
|
-
robot.
|
|
694
|
+
robot.default_vel = 0.5 # back to normal
|
|
678
695
|
|
|
679
|
-
robot.
|
|
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.
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
robot.
|
|
700
|
-
robot.is_protective_stopped()
|
|
701
|
-
robot.is_emergency_stopped()
|
|
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
|
|
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.
|
|
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
|
|