PART 3 Python 으로 배우는 기구학 · 약 45분

12궤적 계획 — 부드럽게 움직이기

관절 공간 보간, 5차 다항식·사다리꼴 속도 프로파일, 직선 경로와 픽앤플레이스 시퀀스를 만듭니다.

왜 순간이동하면 안 될까요#

11장에서는 IK 로 구한 각도를 arm.move_joints() 로 바로 보냈습니다. 시뮬레이터에서는 문제가 없지만, 실물 로봇에게 "지금 0° 인 joint2 를 다음 순간 90° 로!" 라고 명령하면 모터는 온 힘을 다해 그 각도로 가려고 합니다.

  • 큰 가속 → 큰 힘: 팔 전체 무게를 한순간에 휘둘러 모터와 기어에 충격이 갑니다.
  • 흔들림 · 오버슈트: 세게 출발했다 급히 서면서 목표를 지나쳤다 돌아오며 떨립니다.
  • 위험: 들고 있던 물건이 날아가고 주변 사람·물건과 부딪힐 수 있습니다.
❌ 순간이동 (계단) ⭕ 부드러운 이동 (곡선) 각도 속도 시간 → 속도 폭발! → 모터 과부하 · 흔들림 시간 → 속도가 0에서 출발해 0으로 끝남 → 출발·정지가 부드러움 한 제어 주기 만에 목표 각도로 점프
계단처럼 순간이동하면 속도가 치솟고(왼쪽), 곡선으로 나눠 가면 속도가 0 에서 시작해 0 으로 끝납니다(오른쪽).

숫자로 보면 더 분명합니다. 제어기가 1초에 100번(100 Hz) 명령을 보낸다면 명령 간격은 0.01초입니다.

python
import math

dt = 1 / 100                       # 100 Hz 제어 주기
dq = math.radians(90)              # joint2: 0° → 90°

v_jump = dq / dt                   # 한 주기 만에 가려면 필요한 속도
print(f"한 주기에 점프: {v_jump:.0f} rad/s = {math.degrees(v_jump):.0f} °/s")
print(f"2초에 나눠 가기: {dq / 2:.3f} rad/s = {90 / 2:.0f} °/s")
text
한 주기에 점프: 157 rad/s = 9000 °/s
2초에 나눠 가기: 0.785 rad/s = 45 °/s

그래서 로봇은 목표까지 가는 중간 자세들을 촘촘하게 만들어 차례로 보냅니다.

경로(path)와 궤적(trajectory)

경로는 "어떤 길로 가는가"(지나가는 자세의 순서), 궤적은 경로에 시간을 붙인 것, 즉 "언제 어디에 있는가"입니다. 같은 경로라도 천천히 출발하면 다른 궤적입니다. 이 장의 앞부분은 "얼마나 빠르게"(시간 프로파일), 뒷부분은 "어떤 길로"(직선 경로)를 다룹니다.


선형 보간 — 중간 자세 만들기#

출발 자세 q0 에서 목표 q1 까지의 중간은 진행률 s(0 → 1)로 나타냅니다.

text
q(s) = q0 + (q1 − q0) · s        s = 0 이면 출발, s = 1 이면 도착

이것을 선형 보간(linear interpolation, lerp) 이라고 합니다. 관절 6개를 모두 같은 s 로 보간하면 모든 관절이 동시에 출발해 동시에 도착합니다.

python
def lerp_pose(q0, q1, s):
    return [a + (b - a) * s for a, b in zip(q0, q1)]

q0 = [0, 45, 90, 0, 0, 0]
q1 = [40, 70, 110, -30, 20, 0]
for i in range(5):
    s = i / 4
    print(f"s = {s:.2f} →", [round(v, 1) for v in lerp_pose(q0, q1, s)])
text
s = 0.00 → [0.0, 45.0, 90.0, 0.0, 0.0, 0.0]
s = 0.25 → [10.0, 51.2, 95.0, -7.5, 5.0, 0.0]
s = 0.50 → [20.0, 57.5, 100.0, -15.0, 10.0, 0.0]
s = 0.75 → [30.0, 63.8, 105.0, -22.5, 15.0, 0.0]
s = 1.00 → [40.0, 70.0, 110.0, -30.0, 20.0, 0.0]

zip(q0, q1) 은 두 리스트에서 같은 칸끼리 짝지어 꺼내 줍니다. (DM 모델이면 joint2·3 부호를 반대로 넣으세요.)

문제는 s 를 시간에 정비례로 늘리면(s = t / T) 출발하는 순간 속도가 0 에서 갑자기 일정 속도로 튀고, 도착 순간 갑자기 0 이 된다는 것입니다. 속도가 끊기는 곳에서 가속도는 이론상 무한대입니다.


부드러운 출발과 정지 — smoothstep 과 5차 다항식#

해결책은 s 를 시간에 대한 곡선으로 만드는 것입니다. 처음과 끝에서 천천히, 가운데에서 빠르게.

진행률 s(t) — "얼마나 왔나" 속도 ds/dt — "얼마나 빠른가" 10 t = 1 t = 1 1.0 1.5 1.875 선형 s = t smoothstep 3t² − 2t³ 5차 10t³ − 15t⁴ + 6t⁵
세 가지 시간 프로파일. 왼쪽은 진행률, 오른쪽은 속도. 부드러운 프로파일일수록 최고 속도가 평균의 1.5배, 1.875배로 높아집니다.
프로파일s(τ), τ = t/T출발·도착 속도출발·도착 가속도최고 속도 / 평균
선형τ튐무한대1.0
smoothstep (3차)3τ² − 2τ³0튐1.5
5차 다항식 (최소 저크)10τ³ − 15τ⁴ + 6τ⁵001.875

5차 다항식은 속도뿐 아니라 가속도까지 0 에서 출발해 0 으로 끝나서 가장 부드럽습니다. 가속도의 변화율을 저크(jerk) 라 하는데, 저크가 작을수록 덜컹거림이 적어 "최소 저크(minimum jerk)" 궤적이라고도 부릅니다.

python
def linear(t):
    return t

def smoothstep(t):
    return 3 * t**2 - 2 * t**3

def quintic(t):
    return 10 * t**3 - 15 * t**4 + 6 * t**5

N = 10
for name, f in [("linear", linear), ("smoothstep", smoothstep), ("quintic", quintic)]:
    speeds = [(f((i + 1) / N) - f(i / N)) * N for i in range(N)]   # 구간별 속도
    bars = "".join(" ▁▂▃▄▅▆▇█"[min(8, round(v / 1.875 * 8))] for v in speeds)
    print(f"{name:>10} |{bars}|  최고 {max(speeds):.2f}")
text
    linear |▄▄▄▄▄▄▄▄▄▄|  최고 1.00
smoothstep |▁▃▅▆▆▆▆▅▃▁|  최고 1.48
   quintic | ▂▄▇██▇▄▂ |  최고 1.83

글자 막대그래프로 속도 모양을 그려 봤습니다. 선형은 처음부터 끝까지 같은 높이(시작·끝에서 튐), 나머지는 낮게 시작해 낮게 끝납니다. (10칸으로 나눠 잰 평균이라 최고값이 표의 1.5, 1.875 보다 살짝 작게 나옵니다.)


사다리꼴 속도 프로파일#

산업용 로봇과 모터 드라이버에서 가장 흔한 것은 사다리꼴(trapezoidal) 프로파일입니다. 속도 그래프가 사다리꼴 모양입니다.

  1. 가속 구간: 일정한 가속도로 속도를 올립니다.
  2. 등속 구간: 최고 속도 vmax 를 유지합니다.
  3. 감속 구간: 일정한 가속도로 줄여 0 에서 멈춥니다.

장점은 최고 속도와 최대 가속도를 직접 정할 수 있다는 것입니다. "이 관절은 초당 30° 이상, 초당 60°/s² 이상으로는 절대 움직이지 마"를 그대로 반영할 수 있습니다.

python
import math

def trapezoid_time(D, vmax, amax):
    """거리 D 를 vmax·amax 를 지키며 가는 최소 시간과 가속 시간"""
    ta = vmax / amax                        # 최고 속도까지 걸리는 시간
    if D < vmax * ta:                       # 너무 짧아 최고 속도에 못 닿음 → 삼각형
        ta = math.sqrt(D / amax)
        return 2 * ta, ta
    return D / vmax + ta, ta

def trapezoid_s(t, T, ta):
    """시간 t 에서의 진행률 s (0~1)"""
    vp = 1 / (T - ta)                       # 정규화된 최고 속도
    if t < ta:
        return 0.5 * vp / ta * t * t
    if t < T - ta:
        return 0.5 * vp * ta + vp * (t - ta)
    r = T - t
    return 1 - 0.5 * vp / ta * r * r

for D in [90, 10]:                          # 관절을 90°, 10° 움직이기
    T, ta = trapezoid_time(D, vmax=30, amax=60)
    shape = "사다리꼴" if T > 2 * ta + 1e-9 else "삼각형"
    print(f"{D}° 이동: 총 {T:.2f} s, 가속 {ta:.2f} s ({shape})")
    print("   s:", [round(trapezoid_s(T * i / 6, T, ta), 2) for i in range(7)])
text
90° 이동: 총 3.50 s, 가속 0.50 s (사다리꼴)
   s: [0.0, 0.11, 0.31, 0.5, 0.69, 0.89, 1.0]
10° 이동: 총 0.82 s, 가속 0.41 s (삼각형)
   s: [0.0, 0.06, 0.22, 0.5, 0.78, 0.94, 1.0]

짧은 이동(10°)은 최고 속도 30°/s 에 닿기 전에 감속해야 해서 속도 그래프가 삼각형이 됩니다. 이렇게 사다리꼴은 거리에 따라 모양이 자동으로 바뀝니다.

어떤 프로파일을 쓸까?
  • 정해진 시간에 부드럽게: 5차 다항식 (사람 손 동작과 비슷해 시연·데이터 수집에 좋음)
  • 속도·가속도 한계를 정확히 지키며 가장 빨리: 사다리꼴
  • 계산이 간단하고 웬만큼 부드럽게: smoothstep

제어 주기와 이동 시간 계산#

제어 주기 — 점을 몇 개 보낼까#

궤적은 결국 일정한 시간 간격 dt 마다 보낼 자세 목록입니다. 이 간격을 제어 주기, 1/dt 를 제어 주파수(Hz) 라고 합니다. 실물 제어기는 보통 수십 ~ 수백 Hz 로 명령을 보냅니다(공식 SDK 의 실제 주기는 Python SDK 저장소와 위키를 확인하세요). Playground 에서는 dt = 0.05초(20 Hz) 정도면 충분히 부드럽게 보입니다.

python
def quintic(t):
    return 10 * t**3 - 15 * t**4 + 6 * t**5

def plan(q0, q1, T, dt=0.05, profile=quintic):
    """q0 → q1 을 T 초 동안, dt 간격 자세 리스트로"""
    n = max(1, round(T / dt))
    traj = []
    for i in range(n + 1):
        s = profile(i / n)
        traj.append([a + (b - a) * s for a, b in zip(q0, q1)])
    return traj

arm.home()
q0 = arm.get_joints()
q1 = arm.ik(0.40, 0.10, 0.30)            # 목표를 IK 로 구하기 (위치만)
if q1 is None:
    print("IK 실패 — 목표를 바꾸세요")
else:
    dt = 0.05
    traj = plan(q0, q1, T=2.0, dt=dt)
    print("궤적 점 개수:", len(traj))
    vmax = 0
    for a, b in zip(traj, traj[1:]):     # 이웃한 두 점 사이의 관절 속도
        v = max(abs(y - x) for x, y in zip(a, b)) / dt
        vmax = max(vmax, v)
    print(f"가장 빠른 관절 속도: {vmax:.1f} °/s")
    for q in traj[1:]:
        arm.move_joints(q, t=dt)         # 짧은 명령을 차례로 쌓기

시뮬레이터에서 팔이 천천히 출발해 천천히 멈추는지 보세요. 출력된 "가장 빠른 관절 속도"가 생각보다 크다면 T 를 늘려야 합니다. 얼마나 늘려야 하는지는 바로 다음에 계산합니다. profile=quintic 을 lambda t: t(선형)로 바꿔 비교하면 출발·정지 순간의 차이가 보입니다.

속도 한계로 이동 시간 정하기#

지금까지 T 를 "2초"처럼 사람이 정했습니다. 거꾸로 "가장 빠른 순간에도 vmax 를 넘지 않으려면 최소 몇 초?" 를 계산할 수 있습니다. 최고 속도 = k · |Δq| / T 이므로(k 는 위 표의 배수) 다음과 같습니다.

text
T = k · max|Δq| / vmax        (선형 k = 1, smoothstep k = 1.5, 5차 k = 1.875)

관절이 여러 개면 가장 많이 움직이는 관절에 맞춥니다. 모두 같은 s 로 함께 움직이니까요.

python
K = {"linear": 1.0, "smoothstep": 1.5, "quintic": 1.875}

def auto_duration(q0, q1, vmax=30.0, profile="quintic"):
    """모든 관절이 vmax(°/s)를 넘지 않는 가장 짧은 시간(초)"""
    worst = max(abs(b - a) for a, b in zip(q0, q1))
    return max(0.3, K[profile] * worst / vmax)

q0 = [0, 45, 90, 0, 0, 0]
q1 = [60, 80, 120, -30, 20, 0]
for p in K:
    print(f"{p:>10}: {auto_duration(q0, q1, profile=p):.2f} s")
text
    linear: 2.00 s
smoothstep: 3.00 s
   quintic: 3.75 s
URDF 의 속도 값은 "목표 속도"가 아닙니다

공식 URDF 의 <limit velocity="…"> (RS 의 joint1~3 은 50) 는 모델 파일에 적힌 상한일 뿐, 그대로 쓰라는 값이 아닙니다. 처음 실물을 움직일 때는 이 장의 예처럼 초당 수십 도 이하의 느린 속도로 시작하고, 시뮬레이터에서 궤적을 확인한 뒤 조금씩 올리세요. 토크가 켜진 팔의 작업 범위 안에 손을 넣지 말고, 비상정지 수단을 손 닿는 곳에 두는 것도 잊지 마세요(8장).


관절 공간 경로 vs 직교 공간 직선#

두 점 A, B 사이를 움직이는 방법은 두 가지입니다.

위에서 내려다본 모습 (z = 50 mm 높이) 베이스 +x (앞) A (300, −100) B (250, 150) 관절 공간: 손끝 길이 바깥으로 휨 직교 공간: 곧은 직선 ━ 관절 공간 경로 관절 각도 q 를 보간 IK 는 처음·끝 2번만 손끝 길은 곡선 ┅ 직교(카테시안) 공간 손끝 위치 p 를 보간 점마다 IK → 손끝은 직선
위에서 내려다본 모습. 관절 각도를 보간하면 손끝이 휘고(실선), 손끝 위치를 보간하고 점마다 IK 를 풀면 곧은 직선이 됩니다(점선).
관절 공간 (joint space)직교 공간 (Cartesian space)
무엇을 보간?관절 각도 q손끝 위치 p
IK 횟수처음·끝 2번점마다
손끝 길곡선 (예측 어려움)직선
장점빠르고 특이점에 안전길이 예측 가능 (집기·삽입·그리기)
Playgroundmove_joints, move_tomove_line

관절 공간 이동에서 손끝이 얼마나 휘는지 직접 재 봅시다.

python
import math

A = (0.30, -0.10, 0.10)
B = (0.25, 0.15, 0.10)
qA = arm.ik(*A, down=True)
qB = arm.ik(*B, down=True)

def dist_to_line(p, a, b):
    """점 p 에서 선분 a-b 까지의 거리"""
    ab = [b[i] - a[i] for i in range(3)]
    ap = [p[i] - a[i] for i in range(3)]
    t = sum(ab[i] * ap[i] for i in range(3)) / sum(v * v for v in ab)
    t = max(0, min(1, t))
    return math.dist(p, [a[i] + ab[i] * t for i in range(3)])

if qA and qB:
    worst = 0
    for i in range(11):
        s = i / 10
        q = [a + (b - a) * s for a, b in zip(qA, qB)]   # 관절 공간 보간
        p = arm.fk(q)
        worst = max(worst, dist_to_line(p, A, B))
    print(f"관절 공간 이동: 손끝이 직선에서 최대 {worst * 1000:.1f} mm 벗어남")

    arm.move_joints(qA, t=2)
    arm.move_joints(qB, t=2)          # 관절 공간 (곡선)
    arm.move_joints(qA, t=2)
    arm.move_line(*B, t=2, steps=20)  # 직교 공간 (직선)

시뮬레이터의 TCP 축 보기를 켜고 두 번의 이동을 비교해 보세요. 첫 번째는 손끝이 바깥으로 휘어 지나가고(URDF 로 계산해 보면 약 3 cm), 두 번째는 A→B 를 곧게 갑니다. 높이(z)가 같은 두 점인데도 관절 공간 이동에서는 손끝 높이가 출렁일 수도 있습니다. 바닥 가까이에서 움직일 때 이것이 충돌의 원인이 됩니다.

move_line 이 실패하는 경우

직선 위의 모든 점에서 IK 가 풀려야 합니다. 직선이 작업 영역 밖이나 특이점 근처를 지나면 중간에 실패합니다. 큰 이동은 관절 공간으로, 물체 근처의 짧은 이동만 직선으로 하는 것이 실전 요령입니다.


픽앤플레이스 시퀀스 — 큐브 옮기기#

"물체를 집어 다른 곳에 놓기(pick and place)"는 로봇팔의 가장 대표적인 일입니다. 한 번에 짜려면 어렵지만, 거쳐 갈 점 웨이포인트로 쪼개면 하나하나는 지금까지 배운 이동입니다.

책상 블록 (집을 곳) 놓을 곳 홈 1 2 3 4 5 6 7 8 1 물체 위 2 내려가기 3 집기(그리퍼 닫기) 4 들어올리기 5 놓을 곳 위 6 내려가기 7 놓기(그리퍼 열기) 8 올라가기 → 홈 직선 이동(직교 공간) 관절 공간 이동(큰 이동) 그리퍼 명령(팔은 멈춤)
픽앤플레이스를 웨이포인트로 쪼갠 모습. 위아래는 직선, 큰 이동은 관절 공간, 그리퍼 명령 중에는 팔을 멈춥니다.
단계하는 일이동 방식이유
1 접근큐브 바로 위로관절 공간 (move_to)빈 공중이라 휘어도 됨
2 하강큐브 높이로 내려가기직선 (move_line)옆으로 휘면 큐브를 침
3 잡기그리퍼 닫기 + 잠깐 대기팔은 정지움직이며 쥐면 미끄러짐
4 상승다시 위로직선바닥·주변을 긁지 않게
5 이동놓을 곳 위로관절 공간큰 이동은 빠르고 안전하게
6 하강 · 7 놓기 · 8 상승내려가 열고 올라오기직선 / 정지 / 직선2~4 와 같은 이유

이 강좌의 TCP(그리퍼 몸체 원점)는 손가락 끝에 있어서, 그리퍼를 아래로 향하면 TCP 높이가 곧 손가락 끝 높이입니다. 손가락이 실제로 물체를 쥐는 곳은 끝에서 약 2 cm 안쪽입니다. 시뮬레이터의 큐브 잡기 규칙은 단순합니다.

  • 그리퍼 벌림이 22 mm 미만이고, 쥐는 점(TCP 에서 2 cm 안쪽)이 큐브 중심에서 3.5 cm 이내이면 큐브가 붙습니다.
  • 그리퍼를 22 mm 이상 열면 놓습니다. 공중에서 놓으면 바닥(z = 0.02)으로 떨어집니다.

그래서 권장 높이는 다음과 같습니다. 기본 큐브는 (0.40, 0, 0.02), 상자(bin)는 중심 (0.25, 0.32), 높이 0.045 m 에 있습니다.

단계TCP 목표메모
접근큐브 위, z = 큐브 z + 0.07아래 방향 자세는 반경 0.4 m 에서 z 약 0.10 m 까지만 가능(11장)
하강z = 큐브 z + 0.005 (기본 큐브면 0.025)쥐는 점이 큐브 중심 높이에 옴
상승 · 이동z = 0.10상자 벽(0.045)보다 충분히 높게
놓기상자 위 z = 0.07상자 안에서 그리퍼 열기

move_line 은 시작 자세의 접근 방향을 유지한 채 직선으로 움직이므로, 위에서 down=True 로 접근해 두면 하강·상승 동안 그리퍼가 계속 아래를 향합니다.

실습 — Playground 로 큐브 옮기기
  1. 시뮬레이터의 보기 옵션에서 큐브를 켜세요.
  2. 아래 코드를 실행하세요. 큐브를 집어 상자에 넣고 홈으로 돌아옵니다.
python
cube = arm.cube()
if cube is None:
    print("보기 옵션에서 큐브를 켜 주세요.")
else:
    cx, cy, cz = cube
    BIN = (0.25, 0.32)                   # 상자 중심 (x, y)
    APPROACH = cz + 0.07                 # 접근 높이
    GRASP = cz + 0.005                   # 잡는 높이 (TCP = 손가락 끝)
    CARRY = 0.10                         # 들고 다닐 높이
    DROP = 0.07                          # 상자 위에서 놓는 높이
    print(f"큐브 ({cx:.3f}, {cy:.3f}, {cz:.3f}) → 상자 {BIN}")

    def go(x, y, z, t=2.0):
        if not arm.move_to(x, y, z, t=t, down=True):
            raise RuntimeError(f"IK 실패: ({x:.3f}, {y:.3f}, {z:.3f})")

    arm.home()
    arm.open()
    go(cx, cy, APPROACH)                              # 1 접근
    arm.move_line(cx, cy, GRASP, t=1.5, steps=15)     # 2 하강
    arm.close()                                       # 3 잡기
    arm.wait(0.5)
    arm.move_line(cx, cy, CARRY, t=1.5, steps=15)     # 4 상승
    go(BIN[0], BIN[1], CARRY)                         # 5 상자 위로 이동
    arm.move_line(BIN[0], BIN[1], DROP, t=1.5, steps=15)   # 6 하강
    arm.open()                                        # 7 놓기
    arm.wait(0.5)
    arm.move_line(BIN[0], BIN[1], CARRY, t=1.5, steps=15)  # 8 상승
    arm.home()
    print("명령 예약 완료 — 재생을 지켜보세요.")
  1. 재생이 끝나면 print(arm.cube()) 만 따로 한 번 더 실행해 큐브가 상자 근처 (0.25, 0.32) 로 옮겨졌는지 확인하세요. (Playground 는 코드가 끝난 뒤에 동작을 재생하므로, 같은 코드 안에서 arm.cube() 를 다시 불러도 아직 옮기기 전 위치가 나옵니다.)
  2. GRASP 를 cz + 0.05 로 바꿔 다시 해 보세요. 쥐는 점이 큐브에서 3.5 cm 넘게 떨어져 잡히지 않습니다. 또 DROP 을 0.14 그대로 두고 놓으면 큐브가 상자 위 공중에서 떨어집니다.
  3. (도전) APPROACH 를 cz + 0.25 로 올려 보세요. 높이 0.20 m 를 넘으면 RuntimeError: IK 실패 가 납니다. 그리퍼를 아래로 향한 자세가 닿지 않는 높이이기 때문입니다(11장 작업 영역).
실패에 대비하는 시퀀스

위 코드는 IK 가 실패하면 즉시 멈추도록 raise 를 썼습니다. 실물에서 중간 단계 하나가 실패했는데 다음 명령을 계속 보내면, 큐브를 쥔 채 엉뚱한 곳으로 가거나 바닥을 긁을 수 있습니다. 실물에서는 각 단계 전에 목표가 권장 작업영역(70%) 안인지, 접근 높이가 책상·주변 물체보다 충분히 높은지 먼저 확인하세요. 처음 실행은 반드시 시뮬레이터에서, 그다음 실물에서는 느린 속도와 비상정지 준비 상태로 하세요.


정리#

  • 경로 = 지나가는 자세의 순서, 궤적 = 경로 + 시간. 순간이동 대신 촘촘한 중간 자세를 보냅니다.
  • 선형 보간 q0 + (q1 − q0)·s 로 중간 자세를 만들고, s 를 시간 곡선으로 만들면 부드러워집니다.
  • smoothstep(3τ² − 2τ³)은 속도가, 5차 다항식(10τ³ − 15τ⁴ + 6τ⁵)은 가속도까지 0 에서 시작·끝납니다. 최고 속도는 평균의 1.5배, 1.875배.
  • 사다리꼴 프로파일은 vmax·amax 를 직접 지키며, 짧은 이동에서는 삼각형이 됩니다.
  • 이동 시간은 T = k · max|Δq| / vmax 로 가장 많이 움직이는 관절에 맞춰 정합니다.
  • 관절 공간 이동은 손끝이 휘고, 직교 공간 직선(move_line)은 점마다 IK 를 풀어 곧게 갑니다.
  • 픽앤플레이스 = 접근 → 직선 하강 → 잡기 → 직선 상승 → 이동 → 하강 → 놓기 → 상승. 단계마다 실패를 검사합니다.

확인 문제#

  1. 경로와 궤적의 차이는 무엇인가요?
  2. 같은 시간 T 동안 같은 거리를 갈 때, 5차 다항식 프로파일의 최고 속도는 선형의 몇 배인가요?
  3. joint1 을 60°, joint2 를 30° 움직이는 이동을 smoothstep 으로, 최고 속도 30°/s 를 넘지 않게 하려면 최소 몇 초가 필요할까요?
  4. 큐브 바로 위에서 큐브 높이로 내려갈 때 관절 공간 대신 직선 이동을 쓰는 이유는?
  5. Playground 에서 픽앤플레이스 코드 마지막에 print(arm.cube()) 를 넣으면 옮기기 전 위치가 나오는 이유는?
정답
  1. 경로는 지나가는 자세(점)의 순서, 궤적은 그 경로에 "언제 어디에 있는가" 라는 시간 정보를 붙인 것입니다.
  2. 1.875배. (선형은 1배, smoothstep 은 1.5배)
  3. T = 1.5 × 60 / 30 = 3.0초. 가장 많이 움직이는 joint1(60°)에 맞춥니다.
  4. 관절 공간으로 내려가면 손끝이 곡선으로 움직여 손가락이 큐브를 옆에서 칠 수 있기 때문입니다. 직선이면 수직으로 곧게 내려갑니다.
  5. Playground 는 코드를 끝까지 실행한 뒤에 쌓인 동작을 재생하므로, 코드 실행 중에는 큐브가 아직 옮겨지지 않았기 때문입니다.