Quickstart¶
Install¶
pip install ssik # core: library + 13 prebuilt arms + CLI
pip install ssik[urdf] # adds urchin + sympy for ssik build / Manipulator.from_urdf
Python 3.11+. Wheels for Linux x86_64, macOS arm64, macOS x86_64, Windows x86_64.
Use a prebuilt arm¶
from ssik.prebuilt import franka_panda_ik
import numpy as np
T_target = np.eye(4)
T_target[:3, 3] = [0.5, 0.1, 0.3]
sols = franka_panda_ik.solve(T_target) # every analytical IK branch
sols is a list[Solution]. Each Solution carries q (the joint vector), fk_residual (‖FK(q) − T‖_F), and which polish path fired. Empty list = pose is unreachable.
Shipped prebuilts¶
Universal Robots: ssik.prebuilt.universal_robots (11 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `ur5_ik` | Universal Robots UR5 | three-parallel 6R | `base_link` | `ee_link` |
| `ur3e_ik` | Universal Robots UR3e | three-parallel 6R | `base_link` | `tool0` |
| `ur5e_ik` | Universal Robots UR5e | three-parallel 6R | `base_link` | `tool0` |
| `ur10e_ik` | Universal Robots UR10e | three-parallel 6R | `base_link` | `tool0` |
| `ur16e_ik` | Universal Robots UR16e | three-parallel 6R | `base_link` | `tool0` |
| `ur20_ik` | Universal Robots UR20 | three-parallel 6R | `base_link` | `tool0` |
| `ur30_ik` | Universal Robots UR30 | three-parallel 6R | `base_link` | `tool0` |
| `ur7e_ik` | Universal Robots UR7E | three-parallel 6R | `base_link` | `tool0` |
| `ur12e_ik` | Universal Robots UR12E | three-parallel 6R | `base_link` | `tool0` |
| `ur15_ik` | Universal Robots UR15 | three-parallel 6R | `base_link` | `tool0` |
| `ur18_ik` | Universal Robots UR18 | three-parallel 6R | `base_link` | `tool0` |
Unimation: ssik.prebuilt.unimation (1 arm)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `puma560_ik` | KUKA Puma 560 | Pieper 6R (spherical wrist) | `base_link` | `wrist_3_link` |
Kinova: ssik.prebuilt.kinova (5 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `jaco2_ik` | Kinova JACO 2 | non-Pieper 6R | `base_link` | `ee_link` |
| `gen3_ik` | Kinova Gen3 7-DOF | approximate-SRS 7R | `base_link` | `end_effector_link` |
| `gen3_lite_ik` | Kinova Gen3 Lite | non-Pieper 6R | `base_link` | `end_effector_link` |
| `j2s6s300_ik` | Kinova JACO j2s6s300 | Pieper 6R (spherical wrist) | `j2s6s300_link_base` | `j2s6s300_end_effector` |
| `j2s7s300_ik` | Kinova JACO j2s7s300 | approximate-SRS 7R (spherical wrist) | `j2s7s300_link_base` | `j2s7s300_link_7` |
KUKA: ssik.prebuilt.kuka (4 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `iiwa14_ik` | KUKA iiwa LBR 14 | SRS 7R | `base` | `iiwa_link_ee_kuka` |
| `iiwa7_ik` | KUKA iiwa LBR 7 | SRS 7R (offset wrist) | `iiwa_link_0` | `iiwa_link_ee` |
| `kr6_r900_ik` | KUKA KR 6 R900 sixx (Agilus) | Pieper 6R (spherical wrist) | `base_link` | `link_6` |
| `kr210_r2700_ik` | KUKA KR 210 R2700 (Quantec) | Pieper 6R (spherical wrist) | `base_link` | `link_6` |
Franka: ssik.prebuilt.franka (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `panda_ik` | Franka Panda | spherical-shoulder + offset-wrist 7R | `panda_link0` | `panda_link8` |
| `fr3_ik` | Franka Research 3 | spherical-shoulder + offset-wrist 7R (Panda successor) | `fr3_link0` | `fr3_link8` |
UFactory: ssik.prebuilt.ufactory (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `xarm7_ik` | UFactory xArm7 | approximately-spherical-shoulder 7R | `link_base` | `link7` |
| `xarm6_ik` | UFactory xArm6 | non-Pieper 6R (joint 6 y-offset) | `link_base` | `link_eef` |
Unitree: ssik.prebuilt.unitree (1 arm)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `z1_ik` | Unitree Z1 | three-parallel 6R (UR-class) | `link00` | `link06` |
AgileX: ssik.prebuilt.agilex (1 arm)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `piper_ik` | AgileX PiPER | non-Pieper 6R (joints 4 & 6 tilted axis) | `base_link` | `link6` |
Flexiv: ssik.prebuilt.flexiv (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `rizon4_ik` | Flexiv Rizon 4 | non-SRS 7R | `base_link` | `flange` |
| `rizon10_ik` | Flexiv Rizon 10 | non-SRS 7R (~1.4 m reach) | `base_link` | `flange` |
Kassow: ssik.prebuilt.kassow (1 arm)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `kr810_ik` | Kassow KR810 | non-SRS 7R | `base` | `end_effector` |
FANUC: ssik.prebuilt.fanuc (10 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `crx3ia_ik` | FANUC CRX-3iA | non-Pieper 6R (non-spherical wrist) | `base_link` | `tool0` |
| `crx5ia_ik` | FANUC CRX-5iA | non-Pieper 6R (non-spherical wrist) | `base_link` | `tool0` |
| `crx10ia_ik` | FANUC CRX-10iA | non-Pieper 6R (non-spherical wrist) | `base_link` | `tool0` |
| `crx10ialp_ik` | FANUC CRX-10iA/LP | non-Pieper 6R (non-spherical wrist) | `base_link` | `tool0` |
| `crx20ial_ik` | FANUC CRX-20iA/L | non-Pieper 6R (non-spherical wrist) | `base_link` | `tool0` |
| `crx30ia_ik` | FANUC CRX-30iA | non-Pieper 6R (non-spherical wrist) | `base_link` | `tool0` |
| `crx10ial_ik` | FANUC CRX-10iA/L | non-Pieper 6R (non-spherical wrist, 150 mm y-offset) | `base_link` | `tool0` |
| `m710ic_ik` | FANUC M-710iC/70 | Pieper 6R (spherical wrist) | `base_link` | `link_6` |
| `lrmate200id_ik` | FANUC LR Mate 200iD | Pieper 6R (spherical wrist) | `base_link` | `link_6` |
| `r2000ic210l_ik` | FANUC R-2000iC/210L | Pieper 6R (spherical wrist) | `base_link` | `link_6` |
I2RT: ssik.prebuilt.i2rt (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `yam_ik` | I2RT YAM | non-Pieper 6R | `base_link` | `link_6` |
| `big_yam_ik` | I2RT big_yam | non-Pieper 6R | `base` | `gripper` |
Enactic OpenArm: ssik.prebuilt.openarm (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `left_ik` | Enactic OpenArm v2.0 (left) | SRS 7R (non-Z*Z) | `openarm_left_base_link` | `openarm_left_ee_base_link` |
| `right_ik` | Enactic OpenArm v2.0 (right) | SRS 7R (non-Z*Z) | `openarm_right_base_link` | `openarm_right_ee_base_link` |
Galaxea: ssik.prebuilt.galaxea (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `r1pro_left_ik` | Galaxea R1 Pro (left) | SRS 7R (non-Z*Z) | `left_arm_base_link` | `left_arm_link7` |
| `r1pro_right_ik` | Galaxea R1 Pro (right) | SRS 7R (non-Z*Z) | `right_arm_base_link` | `right_arm_link7` |
Standard Bots: ssik.prebuilt.standard_bots (3 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `thor_ik` | Standard Bots Thor | three-parallel 6R | `base_link` | `tool0` |
| `core_ik` | Standard Bots Core | three-parallel 6R | `base_link` | `tool0` |
| `spark_ik` | Standard Bots Spark | three-parallel 6R | `base_link` | `tool0` |
Abb: ssik.prebuilt.abb (5 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `yumi_left_ik` | ABB YuMi (IRB 14000) left | approximate-SRS 7R | `yumi_body` | `yumi_link_7_l` |
| `yumi_right_ik` | ABB YuMi (IRB 14000) right | approximate-SRS 7R | `yumi_body` | `yumi_link_7_r` |
| `irb120_ik` | ABB IRB 120 | Pieper 6R (spherical wrist) | `base_link` | `link_6` |
| `irb1600_ik` | ABB IRB 1600 | Pieper 6R (spherical wrist) | `base_link` | `link_6` |
| `irb6700_ik` | ABB IRB 6700 | Pieper 6R (spherical wrist) | `base_link` | `link_6` |
Yaskawa: ssik.prebuilt.yaskawa (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `gp8_ik` | Yaskawa GP8 | Pieper 6R (spherical wrist) | `base_link` | `link_6_t` |
| `hc10_ik` | Yaskawa HC10 | non-Pieper 6R | `base_link` | `link_6_t` |
Kawasaki: ssik.prebuilt.kawasaki (1 arm)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `rs007n_ik` | Kawasaki RS007N | Pieper 6R (spherical wrist) | `base_link` | `link6` |
Staubli: ssik.prebuilt.staubli (1 arm)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `rx160_ik` | Staubli RX160 | Pieper 6R (spherical wrist) | `base_link` | `link_6` |
Realman: ssik.prebuilt.realman (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `rm75_ik` | Realman RM75 | approximate-SRS 7R | `base_link` | `link_7` |
| `gen72_ik` | Realman GEN72 | approximately-spherical-shoulder 7R | `base_link` | `Link7` |
Dobot: ssik.prebuilt.dobot (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `cr5_ik` | Dobot CR5 | three-parallel 6R (UR-class) | `base_link` | `Link6` |
| `nova5_ik` | Dobot Nova5 | three-parallel 6R (UR-class) | `base_link` | `Link6` |
Mitsubishi: ssik.prebuilt.mitsubishi (1 arm)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `rv4fr_ik` | Mitsubishi RV-4FR | Pieper 6R (spherical wrist) | `rv4fr_base` | `rv4fr_hand_flange` |
Hyundai: ssik.prebuilt.hyundai (1 arm)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `hh020_ik` | Hyundai HH020 | Pieper 6R (spherical wrist) | `base_link` | `tool0` |
Denso: ssik.prebuilt.denso (1 arm)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `vs060_ik` | Denso VS-060 | Pieper 6R (spherical wrist) | `base_link` | `J6` |
Doosan: ssik.prebuilt.doosan (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `m1013_ik` | Doosan M1013 | non-Pieper 6R | `base_link` | `link_6` |
| `m0609_ik` | Doosan M0609 | non-Pieper 6R | `base_link` | `link_6` |
Rokae: ssik.prebuilt.rokae (3 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `xmatepro7_ik` | Rokae xMate Pro7 | SRS 7R | `xMatePro7_base` | `xMatePro7_link7` |
| `xmatecr7_ik` | Rokae xMate CR7 | non-Pieper 6R | `xMateCR7_base` | `xMateCR7_link6` |
| `xmatesr3_ik` | Rokae xMate SR3 | non-Pieper 6R | `xMateSR3_base` | `xMateSR3_link6` |
Trossen: ssik.prebuilt.trossen (2 arms)
| Module | Arm | Class | base_link | ee_link |
|---|---|---|---|---|
| `viperx300s_ik` | Trossen ViperX 300s | Pieper 6R (spherical wrist) | `base_link` | `gripper_link` |
| `widowx250s_ik` | Trossen WidowX 250s | Pieper 6R (spherical wrist) | `wx250s/base_link` | `wx250s/gripper_link` |
Each prebuilt exposes BASE_LINK, EE_LINK, DOF, T_HOME constants so you can verify the baked geometry matches your robot:
from ssik.prebuilt import franka_panda_ik
print(franka_panda_ik.BASE_LINK, "→", franka_panda_ik.EE_LINK)
# base_link → ee_link
print(franka_panda_ik.T_HOME[:3, 3])
# array([0.088, 0., 0.926]) ← matches Franka's documented home
Trajectory tracking pattern¶
For real-time control / teleop, "give me the IK closest to where I am now":
q_current = np.array([0.0, -0.5, 0.0, 0.7, 0.0, 1.2, 0.0])
# max_solutions=1 + q_seed: returns the single solution nearest q_current.
# On 7R jointlock arms the seed also drives the lock-outward fast path
# (~20x faster than the full sweep).
sols = franka_panda_ik.solve(T_target, max_solutions=1, q_seed=q_current)
q_command = sols[0].q if sols else q_current
Two knobs refine what "nearest" means when a seed is given:
# seed_metric (default "wrap_linf"): rank by the LARGEST single-joint move,
# so the arm holds its branch instead of flipping. Use "wrap_l2" for
# summed-distance ranking.
sols = franka_panda_ik.solve(T_target, q_seed=q_current, seed_metric="wrap_linf")
# seed_tolerance (radians): a HARD bound -- only return solutions where every
# joint is within the tolerance of the seed. The list may come back EMPTY,
# which is the signal that smooth continuation isn't possible at this pose
# (replan / accept a larger jump). Best-effort behaviour when omitted.
sols = franka_panda_ik.solve(
T_target, q_seed=q_current, max_solutions=1, seed_tolerance=np.deg2rad(6)
)
if not sols:
... # no config within 6 deg of q_current -- handle the discontinuity
When solve() returns an empty list¶
Use explain=True to attribute the failure:
import ssik
arm = ssik.Manipulator.from_urdf("my_arm.urdf", base="base_link", ee="tool0")
sols, diag = arm.solve(T_target, explain=True)
if not sols:
print(diag.summary())
# solver: ikgeo.three_parallel (tier 0)
# dispatch: Three consecutive parallel axes at joints (1, 2, 3) ...
# -> 0 raw candidates: pose appears unreachable
Distinguishes unreachable (zero raw candidates) from all-filtered (out-of-limits or below FK threshold) from capped (truncated by max_solutions).
Build an artifact for your own arm¶
Build time: - <1 s for tier-0 closed-form (UR-class, Pieper, SRS-class 7R) - ~30 s for non-Pieper 6R (Raghavan–Roth symbolic derivation) - 7–20 min for non-SRS 7R (cached HP per lock sample)
Then import my_arm_ik and use exactly like a prebuilt. See Setting up your robot for the full URDF-to-artifact workflow.