Moduł 12 · analityczne

IK dla geometrii nie-pieperowskich

Jak wyprowadzić zamkniętą IK dla manipulatorów BEZ sferycznego nadgarstka, case study UR5 (Hawkins/Kufieta) i ES5 (Załącznik A dysertacji Gruszki 2024). Plus krótko o redundancji Franka Panda.

O czym jest ten moduł

Moduł 1 pokazał piękne, „klasyczne" wyprowadzenie IK dla PUMA 560, manipulator z trzema osiami nadgarstka schodzącymi w jednym punkcie (forma A warunku Piepera). To pozwoliło na dekompozycję 3+3: najpierw pozycja, potem orientacja. Pięć stron algebry, jeden czysty wynik.

Większość współczesnych cobotów nie spełnia jednak formy A. UR5, UR10, Franka Panda, KUKA iiwa, wszystkie one mają geometrie zaprojektowane pod inne cele (smukłość, lekkość, równa dystrybucja mas), które łamią klasyczną dekompozycję. Czy to znaczy że nie można ich rozwiązać analitycznie? Nie, można, ale trzeba zmienić podejście.

Co wynika z tego modułu

Po przerobieniu modułu student potrafi: (1) rozpoznać formę warunku Piepera na podstawie geometrii DH, (2) wyprowadzić IK dla manipulatora UR5 (forma B, metoda Hawkins/Kufieta), (3) wyprowadzić IK dla ES5 z dysertacji Gruszki, (4) rozumie różnicę między manipulatorami 6-DOF a redundantnym 7-DOF (Franka Panda) i wie dlaczego ten ostatni wymaga zupełnie innego podejścia.

Krok 1

Dwie formy warunku Piepera + przypadek brzegowy

Donald Pieper w 1968 roku pokazał, że istnieje rozwiązanie zamknięte IK dla manipulatora 6-DOF jeśli spełniony jest jeden z dwóch warunków geometrycznych:

  • Forma A, trzy kolejne osie obrotu przecinają się w jednym punkcie.
  • Forma B, trzy kolejne osie obrotu są wzajemnie równoległe.

W obu przypadkach 6-wymiarowy problem rozpada się na dwa niezależne 3-wymiarowe (pozycja + orientacja w jakimś rozumieniu), a wyniki są wzorami zamkniętymi z funkcjami trygonometrycznymi.Jeśli żaden warunek nie zachodzi, IK nadal może być rozwiązalna analitycznie, ale wymaga zaawansowanych metod algebraicznych (Raghavan–Roth, redukcja do równania 16. stopnia w jednej zmiennej).

Forma A

3 osie schodzą w jednym punkcie

3 osiew 1 punkcieTCP

Przykłady: PUMA 560, Stäubli TX, ABB IRB

Forma B

3 osie wzajemnie równoległe

q₂ ∥ q₃ ∥ q₄równoległe osie

Przykłady: UR5/UR10/UR16, ES5, KUKA iiwa (częściowo)

Brak warunku Piepera

Ani A, ani B

ani nie schodzą,ani równoległe

Przykłady: Manipulatory eksperymentalne, wymagana metoda Raghavan–Roth (równanie 16. stopnia)

Zasada Piepera (1968): jeśli manipulator 6-DOF ma 3 kolejne osie spełniające A lub B, to istnieje rozwiązanie zamknięte IK (dekompozycja na 3+3 niewiadome). To warunek wystarczający, nie konieczny, manipulatory bez Piepera też mogą mieć zamknięte rozwiązania, ale wymagają zaawansowanych metod (rezultanty, metoda Raghavan–Roth).

Praktyczna konsekwencja: projektanci robotów zwykle wybierają geometrię spełniającą jedną z form Piepera celowo, żeby IK miało zamknięte rozwiązanie i było obliczane w mikrosekundach zamiast milisekundach. Manipulatory bez Piepera stosuje się rzadko (głównie w eksperymentalnych systemach badawczych).

Krok 2

UR5/UR10/UR16, dlaczego forma A nie działa

Manipulatory Universal Robots (UR3, UR5, UR10, UR16) mają geometrię zaprojektowaną pod cele współpracy z człowiekiem: smukłe, bez ostrych krawędzi, równa dystrybucja masy. Cecha kinematyczna: kolejne osie q₂, q₃, q₄ są wzajemnie równoległe (oś pozioma, w jednym kierunku), a osie nadgarstka są przesunięte względem siebie o niezerowy d5d_5, co oznacza że nie schodzą w jednym punkcie.

Parametry DH UR5 (modyfikowany Craig, [m, rad])

iαi1\alpha_{i-1}ai1a_{i-1}did_iθi\theta_iuwagi
1000,089q1q_1obrót podstawy
2+π/2+\pi/200q2q_2bark
30−0,4250q3q_3forma B: q₂ ∥ q₃ ∥ q₄
40−0,3920,109q4q_4łokieć
5+π/2+\pi/200,095q5q_5d₅ ≠ 0, forma A wykluczona
6π/2-\pi/200,082q6q_6kołnierz

Wniosek: dekompozycja 3+3 z M1 nie zadziała, nie istnieje „środek nadgarstka" jako wspólny punkt 3 ostatnich osi. Ale spełniona jest forma B (q₂ ∥ q₃ ∥ q₄), więc da się rozpisać rozwiązanie zamknięte, tylko innym algorytmem.

Metoda Hawkins/Kufieta (2013/2014): kolejność wyprowadzania to q1q5,q6q2,q3,q4q_1 \to q_5,q_6 \to q_2,q_3,q_4. W skrócie: z geometrii „cylindra zakazanego" wokół podstawy wyznacza się q₁ przez przekształcenia atan2 z d_5 jako stałym offsetem. Mając q₁, można wyizolować q₅ i q₆ z elementów macierzy6R1{}^6R_1. Mając konfigurację kiści, wracamy do podproblemu 3R-planarnego w pionowej płaszczyźnie po q₁, i z twierdzenia cosinusów (jak w M1) wyznaczamy q₂, q₃, q₄.

Liczba rozwiązań: 8, tak samo jak Pumy. Dwa branche shoulder × dwa elbow × dwa wrist. Pomimo zupełnie innej algebry, struktura rozwiązań pozostaje taka sama dla każdego manipulatora spełniającego jakąkolwiek formę Piepera.

▸ Źródła i implementacje referencyjne UR5 IK
  • Hawkins, K. (2013). „Analytic inverse kinematics for the Universal Robots UR-5/UR-10 arms." Technical Report, Georgia Institute of Technology, pierwszy formalny artykuł, pełne wyprowadzenie wszystkich 8 gałęzi.
  • Kufieta, K. (2014). „Force estimation in robotic manipulators: modeling, simulation and experiments." MSc Thesis, NTNU, alternatywne wyprowadzenie z czytelniejszą notacją, używane w wielu open-source implementacjach.
  • Implementacje: ur_kinematics w ROS, ur_rtde w Pythonie, ikfast (autogenerowany z URDF), C++ w ur_robot_driver.

W aplikacji nie implementujemy pełnej IK UR5, to wykraczałoby poza zakres modułu i wymagało osobnego modelu URDF. Skupiamy się tu na ES5 (mamy go już w M9–M11), dla którego mamy gotowe wyprowadzenie z dysertacji [Gruszka 2024, Załącznik A].

Krok 3

ES5 (EasyRobots), geometria i pełne wyprowadzenie z dysertacji

ES5 to manipulator firmy EasyRobots, dla którego M9 (dynamika) i M10 (silnik) korzystają z parametrów inercji. Tu domykamy pętlę, pokazujemy jego kinematyczne rozwiązanie IK. Geometrycznie ES5 jest podobny do UR (forma B, równoległość q₂, q₃, q₄), ale ma inne wymiary i konwencję DH.

Parametry DH ES5 (modyfikowany Craig, [m, rad])

źródło: src/lib/robots/es5.ts

iαi1\alpha_{i-1}ai1a_{i-1}did_iθi\theta_iuwagi
1000q1q_1obrót podstawy
2+π/2+\pi/200q2q_2bark
300,4250q3q_3forma B: q₂ ∥ q₃ ∥ q₄
400,3950,1105q4q_4łokieć z odsadzeniem d₄
5π/2-\pi/200,101q5q_5kostka nadgarstka
6+π/2+\pi/200,0765q6q_6kołnierz końcówki

Kolejność wyprowadzania współrzędnych w dysertacji: θ₁ → θ₅ → θ₆ → θ₃ → θ₂ → θ₄. Ta sekwencja nie jest oczywista, jest algebraicznie wymuszona faktem, że pewne równania trygonometryczne dają się rozwiązać tylko gdy znane są inne współrzędne. To kontrastuje z M1 (Puma), gdzie kolejność q₁ → q₂,₃ → q₄,₅,₆ wynikała wprost z dekompozycji formy A.

Poniżej pełne wyprowadzenie 6 współrzędnych zgodnie z Załącznikiem A dysertacji. Każdy krok zawiera referencję do oryginalnego numeru równania w pracy (eq. A.x).

Punkt wyjścia, zadana macierz transformacji

Zadana jest macierz transformacji TCP względem bazy 6T0{}^6T_0 w postaci ogólnej (eq. A.1 z dysertacji):

6T0=[r11r12r13pxr21r22r23pyr31r32r33pz0001]{}^6T_0 = \begin{bmatrix} r_{11} & r_{12} & r_{13} & p_x \\ r_{21} & r_{22} & r_{23} & p_y \\ r_{31} & r_{32} & r_{33} & p_z \\ 0 & 0 & 0 & 1 \end{bmatrix}

Dla czytelności zapisu używamy w dysertacji skrótu sisinθis_i \equiv \sin\theta_i i cicosθic_i \equiv \cos\theta_i, oraz c23cos(θ2+θ3)c_{23} \equiv \cos(\theta_2+\theta_3), c234cos(θ2+θ3+θ4)c_{234} \equiv \cos(\theta_2+\theta_3+\theta_4) itd. (skutek równoległości osi q₂, q₃, q₄, sumy ich kątów zachowują się jak jeden „efektywny" kąt).

Krok 1

θ₁, z drugiej kolumny przekształconej macierzy

Klucz wyprowadzenia: jeśli weźmiemy 5T1{}^5T_1 obliczone na dwa różne sposoby, analitycznie z parametrów DH, oraz przez izolację z zadanego 6T0{}^6T_0 przez mnożenie odwrotnościami, dostaniemy układ równań w którym pewne komórki zależą tylko od θ₁:

(1T0)16T0(6T5)1=5T1(eq. A.2)({}^1T_0)^{-1} \cdot {}^6T_0 \cdot ({}^6T_5)^{-1} = {}^5T_1 \qquad \text{(eq. A.2)}

Analitycznie (z DH) 5T1{}^5T_1 ma w komórce y wektora translacji (drugi wiersz, czwarta kolumna) wartość stałą: d4-d_4. Z drugiej strony, po wykonaniu mnożenia po lewej i porównaniu, dostajemy równanie w którym jedyną niewiadomą jest θ1\theta_1:

s1(pxd6r13)+c1(pyd6r23)=d4(eq. A.5)-s_1(p_x - d_6 r_{13}) + c_1(p_y - d_6 r_{23}) = -d_4 \qquad \text{(eq. A.5)}

Podstawienie helpers, pozycja środka nadgarstka (środka układu 5) rzutowana na xy bazy:

5p0x=pxd6r13,5p0y=pyd6r23(eq. A.6){}^5p_{0x} = p_x - d_6 r_{13}, \qquad {}^5p_{0y} = p_y - d_6 r_{23} \qquad \text{(eq. A.6)}
5p0xy=(5p0x)2+(5p0y)2,α=atan2(5p0y,5p0x){}^5p_{0xy} = \sqrt{({}^5p_{0x})^2 + ({}^5p_{0y})^2}, \qquad \alpha = \mathrm{atan2}({}^5p_{0y}, {}^5p_{0x})

Po podstawieniu i wykorzystaniu wzoru na sin różnicy kątów:

5p0xysin(θ1α)=d4(eq. A.10){}^5p_{0xy} \sin(\theta_1 - \alpha) = d_4 \qquad \text{(eq. A.10)}

Stąd ostateczna postać dla θ₁:

  θ1=arcsin ⁣(d45p0xy)±atan2(5p0x,5p0y)  (eq. A.13)\boxed{\;\theta_1 = \arcsin\!\Big(\tfrac{d_4}{\,{}^5p_{0xy}\,}\Big) \pm \mathrm{atan2}({}^5p_{0x}, {}^5p_{0y})\;} \qquad \text{(eq. A.13)}
Python · θ₁implementacja eq. A.6–A.13: dwie gałęzie shoulder
import numpy as np

# T_target: macierz 4x4 zadanej pozy efektora
R = T_target[:3, :3]
px, py = T_target[0, 3], T_target[1, 3]
r13, r23 = R[0, 2], R[1, 2]

# eq. A.6: środek układu 5 w bazie (cofamy się o d6 wzdłuż z6_world)
p5x = px - D6 * r13
p5y = py - D6 * r23
p5xy = np.hypot(p5x, p5y)

# eq. A.13: dwie gałęzie shoulder
asin_val = np.arcsin(np.clip(D4 / p5xy, -1, 1))
alpha    = np.arctan2(p5y, p5x)
theta1_candidates = [
    (alpha + asin_val,            "right"),
    (alpha + np.pi - asin_val,    "left"),
]

Dwa rozwiązania, odpowiadają dwóm konfiguracjom barku (shoulder-left / shoulder-right). Analog do gałęzi shoulder w M1.

Krok 2

θ₅, z położenia ostatniego członu względem drugiego

Analiza struktury kinematycznej pokazuje, że współrzędna y wektora 6p1{}^6p_1 (translacja od początku ogniwa 2 do początku ogniwa 6) zależy wyłącznie od θ₅:

6p1y=d4+d6cosθ5(eq. A.14){}^6p_{1y} = d_4 + d_6 \cos\theta_5 \qquad \text{(eq. A.14)}

Z drugiej strony, ten sam 6p1y{}^6p_{1y} można wyrazić przez obrót (1R0)16p0({}^1R_0)^{-1} \cdot {}^6p_0 (eq. A.16–A.18):

6p1y=6p0x(s1)+6p0yc1(eq. A.18){}^6p_{1y} = {}^6p_{0x} \cdot (-s_1) + {}^6p_{0y} \cdot c_1 \qquad \text{(eq. A.18)}

Przyrównanie i wyizolowanie cosθ5\cos\theta_5:

  θ5=±arccos ⁣(6p0xs1+6p0yc1d4d6)  (eq. A.20)\boxed{\;\theta_5 = \pm\arccos\!\Big(\tfrac{-{}^6p_{0x}\,s_1 + {}^6p_{0y}\,c_1 - d_4}{d_6}\Big)\;} \qquad \text{(eq. A.20)}
Python · θ₅dwie gałęzie wrist (flip/noflip)
for theta1, shoulder in theta1_candidates:
    c1, s1 = np.cos(theta1), np.sin(theta1)
    cos5 = (px * s1 - py * c1 - D4) / D6
    if abs(cos5) > 1:
        continue
    base_t5 = np.arccos(np.clip(cos5, -1, 1))
    for wrist_sign in (+1, -1):
        theta5 = wrist_sign * base_t5
        # ... dalej θ₆ i (θ₃, θ₂, θ₄)

Dwa rozwiązania, odpowiadają dwóm orientacjom kiści względem ogniwa 4 (analog do gałęzi wrist flip z M1).

Uwaga o pierwszej osobliwości: gdy θ5=0°\theta_5 = 0°, szósta oś obrotu staje się równoległa do osi 2, 3 i 4, pojawia się nadmiarowość stopni swobody (obrót osi 2, 3, 4 manipuluje TCP niezależnie od rotacji osi 6). To jest klasyczny wrist singularity analogiczny do Pumy.

Krok 3

θ₆, z komórek macierzy 6T1

Wyznaczając jawnie 6T1{}^6T_1 z parametrów DH (eq. A.21) i porównując z formą uzyskaną przez 6R1=(1R0)16R0{}^6R_1 = ({}^1R_0)^{-1}\cdot{}^6R_0 (eq. A.26–A.28), z komórek [2,1] oraz [2,2] dostajemy układ:

{s5c6=s1r11+c1r21,s5s6=s1r12+c1r22.(eq. A.29)\begin{cases} -s_5 c_6 = -s_1 r_{11} + c_1 r_{21}, \\ \phantom{-}s_5 s_6 = -s_1 r_{12} + c_1 r_{22}. \end{cases} \qquad \text{(eq. A.29)}

Stąd osobno sinθ6\sin\theta_6 i cosθ6\cos\theta_6, i ich złożenie przez atan2:

  θ6=atan2 ⁣(s1r12+c1r22s5,  s1r11c1r21s5)  (eq. A.31)\boxed{\;\theta_6 = \mathrm{atan2}\!\Big(\tfrac{-s_1 r_{12} + c_1 r_{22}}{s_5},\; \tfrac{s_1 r_{11} - c_1 r_{21}}{s_5}\Big)\;} \qquad \text{(eq. A.31)}
Python · θ₆atan2(sin θ₆, cos θ₆) z komórek macierzy ⁶R₁
c5, s5 = np.cos(theta5), np.sin(theta5)
r11, r12 = R[0, 0], R[0, 1]
r21, r22 = R[1, 0], R[1, 1]

if abs(s5) < EPS:                        # wrist singularity
    theta6 = 0.0
else:
    sin6 = ( s1 * r12 - c1 * r22) / s5
    cos6 = (-s1 * r11 + c1 * r21) / s5
    theta6 = np.arctan2(sin6, cos6)

atan2 (a nie acos), żeby zachować pełen zakres [π,π][-\pi, \pi]. Dzielenie przez s5s_5 jest niesingularne dopóki θ50\theta_5 \neq 0.

Krok 4

θ₃, z twierdzenia cosinusów w płaszczyźnie x-z

Po znalezieniu θ₁, θ₅, θ₆ wracamy do zadania pozycji. Wyznaczamy 4T1{}^4T_1 przez ciąg mnożeń odwrotnościami:

4T1=(1T0)16T0(5T4)1(6T5)1(eq. A.32){}^4T_1 = ({}^1T_0)^{-1} \cdot {}^6T_0 \cdot ({}^5T_4)^{-1} \cdot ({}^6T_5)^{-1} \qquad \text{(eq. A.32)}

Z geometrii (Rys. A.1 z dysertacji), robot rzutowany na płaszczyznę xz tworzy trójkąt o bokach a2a_2, a3a_3 i przekątnej 4p1|{}^4p_1|. Z twierdzenia cosinusów (jak w M1):

cosβ=a22+a324p122a2a3,β=πθ3\cos\beta = \frac{a_2^2 + a_3^2 - |{}^4p_1|^2}{2 a_2 a_3}, \qquad \beta = \pi - \theta_3

Stąd:

  θ3=±arccos ⁣(a22a32+(4p1x)2+(4p1z)22a2a3)  (eq. A.38)\boxed{\;\theta_3 = \pm\arccos\!\Big(\tfrac{-a_2^2 - a_3^2 + ({}^4p_{1x})^2 + ({}^4p_{1z})^2}{2 a_2 a_3}\Big)\;} \qquad \text{(eq. A.38)}
Python · θ₃trójkąt 2R w płaszczyźnie xz (składowa y wektora ⁴p₁ stała = -d₄)
# Wylicz T_1_4 = (T_0_1)^-1 · T_target · (T_5_6)^-1 · (T_4_5)^-1
T01 = link_transform(0, theta1)
T45 = link_transform(4, theta5)
T56 = link_transform(5, theta6)
T14 = inv_se3(T01) @ T_target @ inv_se3(T56) @ inv_se3(T45)
p1x_4, _, p1z_4 = T14[:3, 3]

# Twierdzenie cosinusów dla trójkąta 2R w płaszczyźnie xz:
a2, a3 = A3, A4                          # ramię i przedramię
p1n2 = p1x_4**2 + p1z_4**2
cos3 = (p1n2 - a2**2 - a3**2) / (2 * a2 * a3)
if abs(cos3) > 1:
    continue
base_t3 = np.arccos(np.clip(cos3, -1, 1))
for elbow_sign in (+1, -1):
    theta3 = elbow_sign * base_t3
    # ... dalej θ₂, θ₄

Dwa rozwiązania, gałęzie elbow-up i elbow-down, analog do M1.

Krok 5

θ₂, z trygonometrii trójkąta O₂O₃O₄

Z tego samego rzutu (Rys. A.1) widzimy: θ2=γα\theta_2 = \gamma - \alpha, gdzie γ to kąt do 4p1{}^4p_1, a α to wewnętrzny kąt trójkąta przy O₂. Wyrażając przez znane wielkości:

γ=atan2(4p1x,4p1z)\gamma = \mathrm{atan2}({}^4p_{1x}, {}^4p_{1z})

α z prawa sinusów dla trójkąta O₂O₃O₄:

sinαa3=sinβ4p1    α=arcsin ⁣(a3sinβ4p1)\frac{\sin\alpha}{a_3} = \frac{\sin\beta}{|{}^4p_1|} \;\Rightarrow\; \alpha = \arcsin\!\Big(\frac{a_3 \sin\beta}{|{}^4p_1|}\Big)

Stąd:

  θ2=atan2(4p1x,4p1z)arcsin ⁣(a3sinβ4p1)  (eq. A.43)\boxed{\;\theta_2 = \mathrm{atan2}({}^4p_{1x}, {}^4p_{1z}) - \arcsin\!\Big(\tfrac{a_3 \sin\beta}{|{}^4p_1|}\Big)\;} \qquad \text{(eq. A.43)}
Python · θ₂rozwiązanie układu liniowego p1x = K·c2 − M·s2, p1z = K·s2 + M·c2
c3, s3 = np.cos(theta3), np.sin(theta3)
K  = a2 + a3 * c3
Mt = a3 * s3
theta2 = np.arctan2(K * p1z_4 - Mt * p1x_4,
                    K * p1x_4 + Mt * p1z_4)
Krok 6

θ₄, z komórek [1,1] i [1,2] macierzy 4T3

Ostatnia współrzędna, analogicznie do θ₆, ale na innym poziomie dekompozycji macierzy:

4T3=(1T0)1(2T1)1(3T2)16T0(5T4)1(6T5)1(eq. A.44){}^4T_3 = ({}^1T_0)^{-1} \cdot ({}^2T_1)^{-1} \cdot ({}^3T_2)^{-1} \cdot {}^6T_0 \cdot ({}^5T_4)^{-1} \cdot ({}^6T_5)^{-1} \qquad \text{(eq. A.44)}

Z komórek [1,1] i [1,2] tej macierzy wyciągamy sinθ4\sin\theta_4 i cosθ4\cos\theta_4 jako kombinacje liniowe znanych już rij,c1,s1,c5,s5,c6,s6,c23,s23r_{ij}, c_1, s_1, c_5, s_5, c_6, s_6, c_{23}, s_{23} (eq. A.45, dwie długie formuły 6-składnikowe). Następnie:

  θ4=atan2(sinθ4,  cosθ4)  (eq. A.46)\boxed{\;\theta_4 = \mathrm{atan2}(\sin\theta_4,\; \cos\theta_4)\;} \qquad \text{(eq. A.46)}
Python · θ₄odczyt z elementów macierzy T_3_4 (α₃=0 → cos θ₄ w [0,0], −sin θ₄ w [0,1])
T12 = link_transform(1, theta2)
T23 = link_transform(2, theta3)
T03 = T01 @ T12 @ T23
T34 = inv_se3(T03) @ T_target @ inv_se3(T56) @ inv_se3(T45)
theta4 = np.arctan2(-T34[0, 1], T34[0, 0])

solutions.append((theta1, theta2, theta3, theta4, theta5, theta6))

Postać końcowa, 6 równań

Komplet wzorów (eq. A.47–A.52 z dysertacji), gotowe do wstawienia do kodu solvera analitycznego dla ES5:

θ1=arcsin ⁣(d45p0xy)±atan2(5p0x,5p0y)\theta_1 = \arcsin\!\Big(\tfrac{d_4}{{}^5p_{0xy}}\Big) \pm \mathrm{atan2}({}^5p_{0x}, {}^5p_{0y})
θ2=atan2(4p1x,4p1z)arcsin ⁣(a3sinβ4p1)\theta_2 = \mathrm{atan2}({}^4p_{1x}, {}^4p_{1z}) - \arcsin\!\Big(\tfrac{a_3 \sin\beta}{|{}^4p_1|}\Big)
θ3=±arccos ⁣(a22a32+(4p0x)2+(4p1z)22a2a3)\theta_3 = \pm\arccos\!\Big(\tfrac{-a_2^2 - a_3^2 + ({}^4p_{0x})^2 + ({}^4p_{1z})^2}{2 a_2 a_3}\Big)
θ4=atan2(sinθ4,  cosθ4)\theta_4 = \mathrm{atan2}(\sin\theta_4,\; \cos\theta_4)
θ5=±arccos ⁣(6p0xs1+6p0yc1d4d6)\theta_5 = \pm\arccos\!\Big(\tfrac{-{}^6p_{0x} s_1 + {}^6p_{0y} c_1 - d_4}{d_6}\Big)
θ6=atan2 ⁣(s1r12+c1r22s5,  s1r11c1r21s5)\theta_6 = \mathrm{atan2}\!\Big(\tfrac{-s_1 r_{12} + c_1 r_{22}}{s_5},\; \tfrac{s_1 r_{11} - c_1 r_{21}}{s_5}\Big)

Liczba rozwiązań: 2 (shoulder θ₁) × 2 (elbow θ₃) × 2 (wrist θ₅) = 8 konfiguracji, identycznie jak dla Pumy 560, choć geometria jest zupełnie inna. To uniwersalny wynik dla manipulatorów 6-DOF spełniających jakąkolwiek formę warunku Piepera.

Krok 4

Porównanie 3 robotów obok siebie

CechaPUMA 560 (M1)UR5 (Hawkins/Kufieta)ES5 (Gruszka 2024)
Forma PieperaA (osie 4,5,6 schodzą)B (osie 2,3,4 równoległe)B (osie 2,3,4 równoległe)
DOF666
Liczba rozwiązań888
Kolejność wyprowadz.q₁ → q₂,q₃ → q₄,q₅,q₆q₁ → q₅,q₆ → q₂,q₃,q₄q₁ → q₅ → q₆ → q₃ → q₂ → q₄
Środek nadgarstkaTAK, w punkcie przecięcia osi 4,5,6NIE, osie nie schodzą (d_5 ≠ 0)NIE, osie nie schodzą
Singularnościoś 1, łokieć wew./zew., nadgarstekcylinder zakazany, łokieć, nadgarstekanalogiczne do UR (θ₅=0 → wrist)
Typowe zastosowanieprzemysł motoryzacyjny, edukacjacobot, lekkie zadania, lead-throughcobot/przemysł, dydaktyka dysertacji

Wniosek dydaktyczny: mimo zupełnie różnych geometrii i algorytmów wyprowadzania, liczba rozwiązań (8) i ogólna struktura wyboru (shoulder × elbow × wrist) są uniwersalne dla każdego manipulatora 6-DOF spełniającego warunek Piepera. To głęboka prawda: forma rozwiązania zależy od geometrii, ale liczba i typ branch'y, od struktury problemu IK.

Krok 5

Bonus: Franka Panda, 7 DOF, redundancja i null-space

Franka Emika Panda łamie wszystkie założenia, na których opierał się ten moduł, bo ma siedem stopni swobody zamiast sześciu. Dla zadania SE(3) (6-DOF) oznacza to nadmiarowość 1 DOF: dla każdej osiągalnej pozy końcówki istnieje cała ciągła rodzina konfiguracji (parametryzowana jednym dodatkowym kątem, zwykle nazywanym kątem łokcia albo swivel angle).

Konsekwencje:

  • Zamknięta IK dla Franki istnieje, ale parametryczna, funkcja θ2(ψ),θ4(ψ),θ6(ψ)\theta_2(\psi), \theta_4(\psi), \theta_6(\psi) jednego wolnego parametru ψ ∈ ℝ.
  • W praktyce do sterowania w pełnym 7-DOF używa się metod jakobianowych z projekcją w null-space, temat M3 (Jakobian Transpose, DLS) i częściowo M7 (analiza singularności). Pseudoinwersja jakobianu daje minimalneq˙\dot q, a null-space pozwala dodatkowo „celować" w ukryte cele (np. unikać przeszkód, optymalizować manipulability).
  • Zalety nadmiarowości: Panda omijająca przeszkody między bazą a celem; możliwość zachowania ergonomicznej konfiguracji łokcia podczas zmiany pozy końcówki; więcej bezpieczeństwa w pobliżu człowieka.

Pełne wyprowadzenie 7-DOF IK wykracza poza ten moduł, wymaga osobnego materiału z parametryzacją kąta łokcia, obsługą singularności łokcia (gdy oś 4 jest prawie wyprostowana) i integracją z null-space optymalizacją. Sugestia: prześledź P. Beeson & B. Ames (2015) „TRAC-IK", niereferencyjna ale praktycznie stosowana biblioteka działająca na Franka, UR i Pumie.

Kompletna funkcja Python, wszystkie kroki sklejone

Sześć snippetów Python z kroków 1–6 wyprowadzenia powyżej, połączone w jedną funkcję solve_es5_ik(T_target). Zwraca listę do 8 rozwiązań (krotki 6 kątów), zgodnie z gałęziami shoulder × elbow × wrist.

Python · pełna implementacjakopiuj-wklej do notatnika i uruchom (wymaga numpy + funkcji DH)
import numpy as np

# Parametry DH ES5 (modified Craig), z src/lib/robots/es5.ts:
A3 = 0.425    # ramię
A4 = 0.395    # przedramię
D4 = 0.1105   # odsadzenie przedramienia
D5 = 0.101    # kostka nadgarstka
D6 = 0.0765   # wystawienie końcówki
EPS = 1e-9

def solve_es5_ik(T_target, link_transform, inv_se3):
    """
    Analityczne IK dla ES5 (forma B Piepera) wg Załącznika A
    dysertacji [Gruszka 2024].

    link_transform(i, theta_i), macierz 4×4 ⁱ⁻¹T_i z DH (Craig).
    inv_se3(T), szybka odwrotność SE(3): R^T blok + -R^T·t.
    Obie funkcje musisz dostarczyć (kilka linijek każda).

    Zwraca: list[tuple[float, ...]], do 8 krotek (q1..q6).
    """
    R = T_target[:3, :3]
    px, py = T_target[0, 3], T_target[1, 3]
    r11, r12, r13 = R[0]
    r21, r22, r23 = R[1]

    # === Krok 1: θ₁ (dwie gałęzie shoulder) ===
    p5x = px - D6 * r13
    p5y = py - D6 * r23
    p5xy = np.hypot(p5x, p5y)
    if p5xy < EPS:
        return []
    ratio = D4 / p5xy
    if abs(ratio) > 1:
        return []
    asin_val = np.arcsin(np.clip(ratio, -1, 1))
    alpha    = np.arctan2(p5y, p5x)
    theta1_candidates = [
        (alpha + asin_val,           "right"),
        (alpha + np.pi - asin_val,   "left"),
    ]

    solutions = []
    for theta1, shoulder in theta1_candidates:
        c1, s1 = np.cos(theta1), np.sin(theta1)

        # === Krok 2: θ₅ (dwie gałęzie wrist) ===
        cos5 = (px * s1 - py * c1 - D4) / D6
        if abs(cos5) > 1:
            continue
        base_t5 = np.arccos(np.clip(cos5, -1, 1))
        for wrist_sign in (+1, -1):
            theta5 = wrist_sign * base_t5
            c5, s5 = np.cos(theta5), np.sin(theta5)

            # === Krok 3: θ₆, atan2(sin θ₆, cos θ₆) z komórek ⁶R₁ ===
            if abs(s5) < EPS:
                theta6 = 0.0
            else:
                sin6 = ( s1 * r12 - c1 * r22) / s5
                cos6 = (-s1 * r11 + c1 * r21) / s5
                theta6 = np.arctan2(sin6, cos6)

            # === T_1_4 numerycznie (cofamy się przez T_5_6, T_4_5) ===
            T01 = link_transform(0, theta1)
            T45 = link_transform(4, theta5)
            T56 = link_transform(5, theta6)
            T14 = inv_se3(T01) @ T_target @ inv_se3(T56) @ inv_se3(T45)
            p1x_4, _, p1z_4 = T14[:3, 3]

            # === Krok 4: θ₃ (dwie gałęzie elbow) ===
            a2, a3 = A3, A4
            p1n2 = p1x_4**2 + p1z_4**2
            cos3 = (p1n2 - a2**2 - a3**2) / (2 * a2 * a3)
            if abs(cos3) > 1:
                continue
            base_t3 = np.arccos(np.clip(cos3, -1, 1))
            for elbow_sign in (+1, -1):
                theta3 = elbow_sign * base_t3

                # === Krok 5: θ₂ (z układu liniowego K·c2 - M·s2 = p1x, ...) ===
                c3, s3 = np.cos(theta3), np.sin(theta3)
                K  = a2 + a3 * c3
                Mt = a3 * s3
                theta2 = np.arctan2(K * p1z_4 - Mt * p1x_4,
                                    K * p1x_4 + Mt * p1z_4)

                # === Krok 6: θ₄ (z elementu T_3_4) ===
                T12 = link_transform(1, theta2)
                T23 = link_transform(2, theta3)
                T03 = T01 @ T12 @ T23
                T34 = inv_se3(T03) @ T_target @ inv_se3(T56) @ inv_se3(T45)
                theta4 = np.arctan2(-T34[0, 1], T34[0, 0])

                solutions.append((theta1, theta2, theta3, theta4, theta5, theta6))

    return solutions

Co warto zauważyć: kolejność wyprowadzania współrzędnych jest algebraicznie wymuszona, najpierw θ₁ z geometrii rzutu, potem θ₅ i θ₆ z analizy macierzy 6T1{}^6T_1, dopiero na końcu θ₃, θ₂, θ₄ z numerycznie wyliczonej 4T1{}^4T_1. To inna kolejność niż klasyczna dekompozycja 3+3 Pumy z M1, bo geometria ES5 (forma B) wymaga innego rozdzielenia.

Referencyjna implementacja w TypeScript

Powyższy algorytm Python jest 1:1 przetłumaczony do TypeScriptu jako src/lib/solvers/analytical-es5.ts w aplikacji. Wersja TS jest używana w playgroundzie poniżej (interaktywne wyznaczanie 8 rozwiązań na żywo). Przechodzi smoke test FK→IK→FK na 5 konfiguracjach z błędem maszynowej precyzji (~10⁻¹⁵ rad).

TypeScriptAPI solvera ES5 w aplikacji
# Uruchom test:
npx tsx src/lib/solvers/__es5_smoke.ts

# Użycie:
import { solveEs5Analytical } from "@/lib/solvers/analytical-es5";
import { forwardKinematics } from "@/lib/robots/dh";
import { ES5 } from "@/lib/robots/es5";

const target = forwardKinematics(ES5, [0.3, 0.4, 0.5, 0.6, 0.7, 0.8]);
const solutions = solveEs5Analytical(target);
// solutions: do 8 rozwiązań z gałęziami shoulder/elbow/wrist

Interaktywny playground, ES5 + 8 rozwiązań IK

Playground ma dwa równoległe stany:

  • q (kąty przegubów), sterowane sliderami po prawej. Robot 3D pokazuje aktualną konfigurację.
  • T* (poza docelowa), wpisywana w panelu „Poza docelowa" jako pozycja (x, y, z) i orientacja RPY. Solver liczy IK z T*, pokazując wszystkie konfiguracje, którymi można trafić w tę pozę.

Wskaźnik TCP odbiega od T* o ... mm mówi czy aktualne q faktycznie trafia w T*. Kliknięcie wiersza w tabeli rozwiązań ładuje wybraną gałąź do sliderów, robot ustawia się tam i wskaźnik staje się zielony.

Eksperymenty do wypróbowania:

  • Wpisz pozycję z = 0.05 (TCP nisko nad podłożem), większość rozwiązań prawdopodobnie odpadnie (poza zasięgiem ramienia w dół). Zobaczysz krótszą listę albo komunikat „brak rozwiązań".
  • Kliknij ← zrzut FK(q), potem przesuń sliderem θ₅ blisko 0 i ponownie zrób zrzut, wokół tej pozy gałęzie wrist (flip/noflip) zaczynają się zlewać: ta sama orientacja osiągalna z różnymi θ₄ i θ₆. To singularność nadgarstka.
  • Kliknij wiersz „shoulder=left", robot „obraca się dookoła osi 1" w symetryczną konfigurację. Ten sam TCP, zupełnie inna postawa ramienia.
  • Kliknij wiersz „elbow=down", łokieć ląduje pod linią bark↔nadgarstek. W praktyce takie konfiguracje są rzadziej używane przemysłowo (mniej ergonomiczne, większe ryzyko kolizji z otoczeniem).

Konfiguracja q [°]

θ₁0.0°
θ₂0.0°
θ₃0.0°
θ₄0.0°
θ₅0.0°
θ₆0.0°

Pose docelowa T*

Pozycja [m]
Orientacja RPY [°]
✓ TCP na pozie docelowej (|Δp| < 1 mm)

Rozwiązania IK z pozy docelowej: 0(maks. 8, shoulder × elbow × wrist)

kliknij żeby załadować

Brak rozwiązań, poza zasięgiem albo geometryczna degeneracja. Spróbuj zmienić pozycję lub orientację w panelu po prawej.

S=shoulder (left/right), E=elbow (up/down), W=wrist (flip/noflip). Wiersz w kolorze zielonym to gałąź, do której aktualnie ustawione są przeguby. Klikając inny wiersz załadujesz tę konfigurację, robot trafi w tę samą pozę docelową inną drogą.

Co dalej

  • Solver ES5 (analytical-es5.ts) jest gotowy, może być teraz wpięty do M6 (Benchmark) jako drugi analityk obok Pumy.
  • M3 (Jakobianowe) , metody iteracyjne nie wymagają spełnienia warunku Piepera, działają dla dowolnej geometrii (Franka, UR, custom). To uniwersalne rozwiązanie dla przypadków, w których wyprowadzenie analityczne jest zbyt skomplikowane.
  • M6 (Benchmark) , porównanie analityka vs iteracja na konkretnych przypadkach testowych. Analityk wygrywa w prędkości (µs vs ms), iteracja w uniwersalności.