Skip to content

Commit 06b23d4

Browse files
committed
add global ik_reference to prevent elbow/wrist flipping
- Global ik_reference on URRobot (constructor, property, from_config) - ik_reference in config.yaml for persistent setup - ik_reference in CLI teach pendant (reads config, shows at boot) - Per-call ik_reference parameter on move_to, move_relative, move_sequence - README documentation with recommendation to always use ik_reference - 21 unit tests for move_sequence with ik_reference
1 parent 03c99bf commit 06b23d4

8 files changed

Lines changed: 654 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.3.20] — 2026-07-27
9+
10+
### Added
11+
- Global `ik_reference` setting on `URRobot` — set once, applies to all motion commands (`move_to`, `move_relative`, `move_sequence`) to prevent elbow/wrist flipping (IK ambiguity)
12+
- `ik_reference` in config.yaml and `from_config()` — persistent setup without code changes
13+
- `ik_reference` in CLI teach pendant — reads from config.yaml, shows at boot
14+
- Per-call `ik_reference` parameter on `move_to()`, `move_relative()`, `move_sequence()` — override global default per move
15+
- README documentation with recommendation to always use `ik_reference` for stable motion
16+
817
## [0.3.19] — 2026-07-27
918

1019
### Changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,43 @@ robot.move_relative(delta_z=0.05) # 5cm along tool Z
398398
- **BASE** (default): delta relative to robot base
399399
- **TOOL**: delta relative to TCP orientation
400400

401+
#### IK Reference (recommended)
402+
403+
**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.
404+
405+
**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.
406+
407+
```python
408+
# 1. Put the robot in your preferred posture (e.g., "home")
409+
# 2. Save it: robot.save_point("home")
410+
# 3. Set as IK reference:
411+
robot.ik_reference = "home"
412+
413+
# Now all moves stay close to that posture — no elbow flipping
414+
robot.move_to("pick")
415+
robot.move_to("place")
416+
robot.move_relative(delta_z=-0.05)
417+
```
418+
419+
**In config.yaml** (recommended for permanent setup):
420+
421+
```yaml
422+
robot_ip: 192.168.1.50
423+
ik_reference: home # prevents weird elbow/wrist flips
424+
```
425+
426+
**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.
427+
428+
**Per-move override:**
429+
430+
```python
431+
robot.ik_reference = "home" # global default
432+
robot.move_to("weird_pose", ik_reference=None) # one move without it
433+
robot.move_to("back", ik_reference="current") # use current joints
434+
```
435+
436+
**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.
437+
401438
#### Points are tool-agnostic
402439

403440
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.
@@ -563,6 +600,7 @@ URKit searches for `config.yaml` in this order:
563600
| `robot_ip` | Robot IP address | `192.168.1.50` |
564601
| `points_path` | Path to SQLite points database | `points.db` |
565602
| `gripper` | Gripper preset name | `hand-e`, `2f-85`, `2f-140`, `digital` |
603+
| `ik_reference` | IK reference posture (prevents elbow flipping) | `home` |
566604
| `default_vel` | Default linear velocity (m/s) | `0.5` |
567605
| `default_acc` | Default linear acceleration (m/s²) | `0.3` |
568606
| `expert_mode` | Disable safety speed clamping | `false` |

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "urkit"
7-
version = "0.3.19"
7+
version = "0.3.20"
88
description = "Universal Robots e-Series control toolkit built on ur_rtde"
99
readme = "README.md"
1010
license = {text = "MIT"}

src/urkit/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
from __future__ import annotations
2626

27-
__version__ = "0.3.19"
27+
__version__ = "0.3.20"
2828

2929
from urkit.config import load_config, resolve_config
3030
from urkit.exceptions import (

src/urkit/cli/teach.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,12 @@ def teach_command(args) -> None:
13861386
if gripper_name and isinstance(gripper_name, str):
13871387
gripper_name = gripper_name.lower().replace("_", "-")
13881388
points_path = args.points or config.get("points_path") or "points.db"
1389+
raw_ik = config.get("ik_reference")
1390+
ik_reference: str | list[float] | None = None
1391+
if isinstance(raw_ik, str):
1392+
ik_reference = raw_ik
1393+
elif isinstance(raw_ik, list) and len(raw_ik) == 6:
1394+
ik_reference = raw_ik
13891395

13901396
# Resolve gripper constructor params from config.yaml gripper_config section
13911397
# and CLI --gripper-* flags (CLI overrides config)
@@ -1454,6 +1460,8 @@ def teach_command(args) -> None:
14541460
if gripper_name:
14551461
print(f" Gripper: {gripper_name}")
14561462
print(f" Points: {points_path}")
1463+
if ik_reference:
1464+
print(f" IK ref: {ik_reference}")
14571465

14581466
# URRobot handles everything: safety recovery, remote mode check,
14591467
# power on, brake release, program stop, and RTDE connection.
@@ -1462,6 +1470,7 @@ def teach_command(args) -> None:
14621470
ip=ip, # type: ignore
14631471
points=points_path, # type: ignore
14641472
gripper=gripper_config,
1473+
ik_reference=ik_reference,
14651474
**gripper_kwargs,
14661475
)
14671476
print(" Connected.", flush=True)

src/urkit/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
max_mm: 50 # finger travel
6161
default_vel: 0.5 # m/s
6262
default_acc: 0.3 # m/s²
63+
ik_reference: home # point name for IK reference posture
6364
expert_mode: false # show advanced CLI commands
6465
"""
6566

0 commit comments

Comments
 (0)