LOG152 / Motion interpolation architecture

8点のモーションデータから
補間軌跡を生成し、
UnityとPiで共有する

パイプライン(クリックで詳細)

SOURCE
motion_log_block.csv
実機記録・8点
8 frames
↓
INTERPOLATE / Mac側
interpolate_motion.py
EaseInOut補間 × 10ステップ
t = t²(3−2t)
↓
OUTPUT
motion_log_interpolated.csv
補間済み・81点
81 frames
↓
UNITY
VariationMotionPlayer.cs
20体がそれぞれ異なるオフセットで再生
robotIndex × 2° / 1°
Pi(LOG153〜)
pick_and_place_fixed.py
同じCSVをsend_angles()で順送り
send_angles()
// SOURCE: motion_log_block.csv
実機mechArm 270-Piで記録した8姿勢のモーションログ。各行にタイムスタンプ・ラベル・J1〜J6の関節角度が記録されている。
timestamp,label,J1,J2,J3,J4,J5,J6
0.0,HOME,0,0,0,0,0,-30
1.0,GRIPPER_OPEN,0,0,0,0,0,-30
2.0,APPROACH_PICK,-54.22,81.91,-30.14,-8.08,-50.27,-29.44
3.0,PICK,-46.93,102.74,-43.33,3.6,-60.11,-31.9
4.0,GRIPPER_CLOSE,-46.93,102.74,-43.33,3.6,-60.11,-31.9
5.0,APPROACH_PLACE,59.85,30.0,27.94,10.01,-45.17,-35.33
6.0,PLACE,54.49,45.17,26.63,2.9,-70.57,-29.26
7.0,GRIPPER_OPEN_PLACE,59.85,30.0,27.94,10.01,-45.17,-35.33
8.0,HOME_RETURN,0,0,0,0,0,-30
// INTERPOLATE: interpolate_motion.py
隣接フレーム間を三次EaseInOutで10ステップに分割。線形補間(等速)では動き始めと終わりにカクつきが出るため、SmoothStepを使う。
def ease_in_out(t):
    return t * t * (3 - 2 * t)

for s in range(STEPS):  # STEPS = 10
    t_linear = s / STEPS
    t_eased = ease_in_out(t_linear)
    angles = [
        round(a + (b - a) * t_eased, 2)
        for a, b in zip(angles_from, angles_to)
    ]
// OUTPUT: motion_log_interpolated.csv
8区間 × 10ステップ + 最終フレーム1行 = 81行。ラベルは元のラベル + ステップ番号(例:APPROACH_PICK_i3)。
timestamp,label,J1,J2,J3,J4,J5,J6
...,HOME_i0,0.0,0.0,0.0,0.0,0.0,-30.0
...,GRIPPER_OPEN_i0,0.0,0.0,0.0,0.0,0.0,-30.0
...,APPROACH_PICK_i0,-0.63,0.95,-0.35,-0.09,-0.58,-29.44
...,APPROACH_PICK_i1,-2.38,3.59,-1.33,-0.35,-2.2,-29.45
...,APPROACH_PICK_i2,-5.06,7.64,-2.84,-0.74,-4.69,-29.46
...(81行)
// UNITY: VariationMotionPlayer.cs
20体のデジタルツインが1°刻りのオフセットを持ちながら再生。APPROACH_PLACE以降はオフセットなし(全体が同一PLACEに収束)。
// J1/J2オフセット(PICK側のみ)
bool applyOffset = !label.Contains("APPROACH_PLACE")
                && !label.Contains("PLACE_i")
                && !label.Contains("GRIPPER_OPEN_PLACE")
                && !label.Contains("HOME_RETURN")
                && label != "PLACE";

if (applyOffset)
{
    if (j == 0) angle += robotIndex * offsetDegPerIndex * 2f;
    if (j == 1) angle += robotIndex * offsetDegPerIndex * 1f;
}

// 再生開始タイミングをずらす(ウェーブ効果)
Invoke(nameof(Play), (robotIndex - 1) * 0.5f);
// Pi(LOG153〜): pick_and_place_fixed.py
同じmotion_log_interpolated.csvを読み込んでsend_angles()で順送りする。UnityとPiが同じデータを使うことで、シミュレーションで確認した動きがそのまま実機に乗る。
# DAY153実装予定
for frame in frames:
    angles = frame[2:8]  # J1〜J6
    mc.send_angles(angles, speed=20)
    time.sleep(0.1)  # stepInterval相当

補間前後の比較

補間前(原データ)
フレーム数 8
フレーム間隔 固定 1.5s
補間カーブ なし
最大角度差/フレーム 106°
補間後(出力データ)
フレーム数 81
フレーム間隔 角度差に比例
補間カーブ EaseInOut(三次)
最大角度差/フレーム ≤ 11°

フレーム間最大関節角度差(J1基準)

HOME → GRIPPER_OPEN
0°
GRIPPER_OPEN → APPROACH_PICK
54°
APPROACH_PICK → PICK
7°
PICK → GRIPPER_CLOSE
0°
GRIPPER_CLOSE → APPROACH_PLACE
107°
APPROACH_PLACE → PLACE
5°
PLACE → GRIPPER_OPEN_PLACE
5°
GRIPPER_OPEN_PLACE → HOME_RETURN
60°