97mmの乖離は、fk_scan_motion_based.csv のコピーし忘れだった話
4つの仮説を順に否定して、5つ目が正解だった。
💡 Key Takeaway
FKスキャンが記録したee座標と、Unity上で実測したBrep位置が97mm離れていた。4つの仮説を立て、順番に潰していった。全部外れた。残った5つ目が正解で、Mac側の fk_scan_motion_based.csv が数週間前の改修前のまま放置されていただけだった。1コマンドで上書きして、lookup最近傍距離は71.10mm → 0.30mmになった。
やったこと | 起きたこと | 判断 |
|---|---|---|
FKScanner_MotionBased.cs を読んだ | コードは構造的に正しかった | 仮説A(並列実行誤差)を否定 |
Brep の Hierarchy パスを突き合わせた | 同じ Brep を参照していた | 仮説B/C(参照ズレ)を否定 |
TipBrepReader の Wait を 3秒 → 0.5秒に変えた | 結果は 1.4mm しか変わらなかった | 仮説D(物理収束時間)を否定 |
Mac側とUnity側のCSVのタイムスタンプを比較した | 1日違いで中身が別物だった | 仮説E(CSVが古い)を確定 |
Unity側CSVをMac側にコピーした | XZ最近傍が 71.10mm → 0.30mm | 案A完了 |
97mm、原因不明から始まった
前日のセッションで fk_scan_motion_based.csv に書かれたee座標と、TipBrepReaderで実測した同anglesのBrep中点位置が97mm離れていることが判明した。
angles = [-50, 100, -55, -2, -35, -20]
CSV ee = (0.183, 0.042, 0.154)
実測 ee = (0.252, 0.018, 0.218)
3D距離 = 約 97mm「同じアームの、同じ姿勢で、なんでここまで違う」
候補はいくらでも挙げられた。並列実行の誤差、物理シミュのタイミング、Brepの参照ズレ、座標系の根本的な不一致。前日に追加したばかりのP008を思い出した。測れる手段を持つことが、推論より先にくる。手当たり次第に推論するより先に、何が測れるかを整理した。
4つの仮説を立て、順に消した
Claudeと一緒に原因候補を4つに絞った。
仮説 | 内容 |
|---|---|
A | 20体並列実行時の座標計算誤差 |
B/C | 参照Brepの不一致(リネーム作業の影響) |
D | WaitForSeconds(0.5f) が短くて物理収束が間に合わない |
E | CSVが生成時のまま古くなっている |
Claudeは「Dが一番怪しい」と言った。FKスキャナは0.5秒で読んでいるのに、TipBrepReaderは3秒待つ。並列負荷下でその差が効いている可能性。
仮説Aを消した。 20体のWorld Rotationを全部確認したら全員(0,0,0)。Positionの差は相対座標化で吸収されるので問題ない。
仮説B/Cを消した。 TipBrepReaderのInspectorでleftTipBrep / rightTipBrepをダブルクリックして参照先を辿った。FKScanner_MotionBasedの FindGripperTipBrep が辿るパス(gripper_left1 → ... → Instance-Def-210103_4302_101 → Brep)と完全一致。同じBrepを見ている。
仮説Dを消した。 TipBrepReaderの Wait Seconds After Set を3秒から0.5秒に変えて再実行した。
Wait 3秒 : Midpoint = (0.251820, 0.018492, 0.217731)
Wait 0.5秒: Midpoint = (0.251547, 0.017351, 0.217040)
差 : 約 1.4mm1.4mm。97mm乖離の説明にならない。3つ消えて、仮説が枯渇した。
過去に起きていたことを遡った
Claudeが過去記事を検索した。数週間前のセッションに、こういう発覚が記録されていた。
「213,312点が、実は手首の位置だった」
それまでのFKスキャナーは endEffector.position - root.position、つまりlink6フランジ(手首)の位置をeeとして記録していた。グリッパ先端ではなかった。気づいたときスキャナー4本を書き換えて、座標系を「グリッパ先端Brep中点」に統一し直した。深夜0時過ぎまでかけて4回スキャンを回し直した日だった。
その改修前後のee差を計算してみた。
CSV 値(旧、手首基準) : (0.183, 0.042, 0.154)
TipBrepReader 実測(新): (0.252, 0.018, 0.218)
差 : (+69, -24, +64) mmグリッパの寸法そのものだった。link6フランジからグリッパ先端Brep中点までのオフセット。
仮説Eの輪郭が見えた。改修日にUnity側のスキャナーは更新された。Unity側の Assets/Data/ にある CSV も更新された。でも Mac側の r2r2r-scripts/ にある CSV が、改修前の手首基準のまま残っているのではないか。
ファイルのタイムスタンプが答えを持っていた
ls -la で両方のCSVを比較した。
ls -la ~/r2r2r-scripts/fk_scan_motion_based.csv
ls -la ~/Robotics_UX/Robotics_UX_New/Assets/Data/fk_scan_motion_based.csv
wc -l ~/r2r2r-scripts/fk_scan_motion_based.csv
wc -l ~/Robotics_UX/Robotics_UX_New/Assets/Data/fk_scan_motion_based.csvMac 側 : 4月18日 10:54、15.7MB、213,313行
Unity 側: 4月19日 06:27、37.5MB、512,088行1日違い。サイズも行数も別物。同anglesの行をgrepで引いて並べた。
grep "^[^,]*,[^,]*,[^,]*,-50.000,100.000,-55.000,-2.000,-35.000,-20.000" \
~/r2r2r-scripts/fk_scan_motion_based.csv
grep "^[^,]*,[^,]*,[^,]*,-50.000,100.000,-55.000,-2.000,-35.000,-20.000" \
~/Robotics_UX/Robotics_UX_New/Assets/Data/fk_scan_motion_based.csvMac 側 : ee = (0.182957, 0.041725, 0.153837)
Unity 側: ee = (0.252898, 0.018800, 0.219693)Unity側の値とTipBrepReader実測値の差は1〜2mm。ほぼ一致。
仮説E確定。 手首位置基準の古いCSVがMac側に残っていた。Piの r2r2r_loop.py がそれを参照していたから、lookupで返される姿勢は「手首がブロック中心に来る」angles、つまりグリッパ先端はそこから97mmズレた場所にあった。
1コマンドで終わった
旧CSVをバックアップしてからコピーした。
cp ~/r2r2r-scripts/fk_scan_motion_based.csv \
~/r2r2r-scripts/fk_scan_motion_based.csv.bak_DAY170_pre_overwrite_$(date +%Y%m%d_%H%M%S)
cp ~/Robotics_UX/Robotics_UX_New/Assets/Data/fk_scan_motion_based.csv \
~/r2r2r-scripts/fk_scan_motion_based.csv5秒で完了した。
71mm が 0.3mm になった
verify_lookup_day169.py を走らせて効果を確認した。
cd ~/r2r2r-scripts && python3 verify_lookup_day169.py比較 | 旧CSV | 新CSV | 改善 |
|---|---|---|---|
総行数 | 213,312 | 512,087 | 2.4倍 |
3D最近傍 | 82.98mm | 1.02mm | 81倍 |
XZ最近傍 | 71.10mm | 0.30mm | 237倍 |
座標系を改修した日にスキャン直後で出ていた精度(赤ブロック0.30mm)と同じ数値が、ようやくPi側のループからも引けるようになった。
「ロボットが正確な位置に届かない」と感じていた事象の少なくとも一部は、これで説明がつく。CSVの中に最初から正しい姿勢がなかった。引いていたlookup table自体が、別物だった。
見えていなかったのは1点だけだった
過去のセッションで「爪オフセット問題(約90mm)」として記録されていた事象と、今回の97mm。数字がほぼ同じだった。同じ問題を、違う角度から何度も観測していた。
スキャナーの改修はやった。CSVの再生成もやった。座標系の統一もやった。全部「やった」。
Mac側へのコピーだけが「やらなかった」。
数週間、その1コマンドがプロジェクトの中心に居座っていた。スキャナーが正しいことも、Unity側のCSVが更新されていることも、それぞれは正しく把握していた。「Mac側に届いていない」という1点だけが視野から抜けていた。
ミスはいつもこういう形をしている。技術的に難しい問題じゃない。気づくのが難しい問題だった。
前日に「測れる手段」を作っておいたから、4つの仮説を測定で順番に消せた。もし推論で繋いでいたら、たぶん今も的外れな対応を続けていた。
P008が動いた瞬間だった。
残る課題
新CSVでも、前日に手動探索したPICK angles [-49, 98, -32, 0, -65, 0]は引けない。j5が起点姿勢のまま固定されているのでCSVに存在しない。
新CSV top1(3D最近傍 1.02mm):
angles = [-40, 105, -50, 23, -45, -20]
ee = (0.240339, 0.011132, 0.213523)このanglesで実機を動かせばグリッパ先端は赤ブロック中心の1mm以内に到達するはず。掴めるかは別で、j5がブロックの向きと合っていないと爪が素通りする可能性がある。
r2r2r_loop.py のlookup方式(XZ距離のみ)の問題も見えた。XZで最近傍を取ると、3Dでは32mmズレるケースがある。Y方向の制約が必要だった。
次のセッションで取り組む。
検証で使用・生成したコード
検証プロセス全体を再現できるよう、本セッションで使用・生成したコードを掲載する。Unity Editor 操作のうちコード化されていないもの(Inspector のフィールド設定など)は、各 H2 セクションの記述を参照のこと。
TipBrepReader.cs(前セッションで生成、本セッションで仮説D検証に使用)
任意 angles に対して leftTipBrep / rightTipBrep の World Position を実測する Editor ツール。本セッションでは Wait Seconds After Set を 3 → 0.5 に変えて再実行することで仮説D(物理収束時間)を否定した。
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// TipBrepReader
/// 任意 angles を入力 → 姿勢を Unity でセット → leftTipBrep/rightTipBrep の World 実位置を読み取り
/// 物理憲法遵守: drive.target のみ書き込み
/// </summary>
public class TipBrepReader : MonoBehaviour
{
[Header("PICK姿勢角度(度、6要素)")]
public float[] angles = new float[6] { -50f, 100f, -55f, -2f, -35f, -20f };
[Header("アーム参照")]
public ArticulationBody rootArticulationBody;
public ArticulationBody[] joints = new ArticulationBody[6];
[Header("爪先Brep参照")]
public Transform leftTipBrep;
public Transform rightTipBrep;
[Header("設定")]
public float waitSecondsAfterSet = 3f;
public bool runOnStart = false;
void Start()
{
if (runOnStart)
{
StartCoroutine(ReadTipBrepCoroutine());
}
}
[ContextMenu("Read Tip Brep Positions")]
public void ReadTipBrep()
{
if (!Application.isPlaying)
{
Debug.LogError("[TipBrepReader] Playモードで実行してください");
return;
}
StartCoroutine(ReadTipBrepCoroutine());
}
private System.Collections.IEnumerator ReadTipBrepCoroutine()
{
if (rootArticulationBody == null)
{
Debug.LogError("[TipBrepReader] rootArticulationBody が未設定");
yield break;
}
if (joints == null || joints.Length < 6)
{
Debug.LogError("[TipBrepReader] joints が未設定または6要素未満");
yield break;
}
if (leftTipBrep == null || rightTipBrep == null)
{
Debug.LogError("[TipBrepReader] leftTipBrep / rightTipBrep が未設定");
yield break;
}
if (angles == null || angles.Length < 6)
{
Debug.LogError("[TipBrepReader] angles は6要素必要");
yield break;
}
// 姿勢セット(SetJointPositions で瞬時、drive.target で物理保持)
var positions = new List<float>();
for (int i = 0; i < 6; i++)
positions.Add(angles[i] * Mathf.Deg2Rad);
rootArticulationBody.SetJointPositions(positions);
Physics.SyncTransforms();
for (int i = 0; i < 6; i++)
{
if (joints[i] == null) continue;
var drive = joints[i].xDrive;
drive.target = angles[i];
joints[i].xDrive = drive;
joints[i].SetDriveTarget(ArticulationDriveAxis.X, drive.target);
}
Debug.Log($"[TipBrepReader] 姿勢セット完了。{waitSecondsAfterSet}秒待機中...");
yield return new WaitForSeconds(waitSecondsAfterSet);
// 読み取り
Vector3 leftPos = leftTipBrep.position;
Vector3 rightPos = rightTipBrep.position;
Vector3 midpoint = (leftPos + rightPos) * 0.5f;
Vector3 rootPos = rootArticulationBody.transform.position;
Vector3 ee = midpoint - rootPos;
Debug.Log($"[TipBrepReader] === PICK姿勢 Transform 読み取り結果 ===");
Debug.Log($"[TipBrepReader] angles: [{angles[0]:F2}, {angles[1]:F2}, {angles[2]:F2}, " +
$"{angles[3]:F2}, {angles[4]:F2}, {angles[5]:F2}]");
Debug.Log($"[TipBrepReader] leftTipBrep World Position: (X, Y, Z) = " +
$"({leftPos.x:F6}, {leftPos.y:F6}, {leftPos.z:F6})");
Debug.Log($"[TipBrepReader] rightTipBrep World Position: (X, Y, Z) = " +
$"({rightPos.x:F6}, {rightPos.y:F6}, {rightPos.z:F6})");
Debug.Log($"[TipBrepReader] Midpoint World Position: (X, Y, Z) = " +
$"({midpoint.x:F6}, {midpoint.y:F6}, {midpoint.z:F6})");
Debug.Log($"[TipBrepReader] Root World Position: (X, Y, Z) = " +
$"({rootPos.x:F6}, {rootPos.y:F6}, {rootPos.z:F6})");
Debug.Log($"[TipBrepReader] EE (Midpoint - Root) : (X, Y, Z) = " +
$"({ee.x:F6}, {ee.y:F6}, {ee.z:F6})");
Debug.Log($"[TipBrepReader] === 読み取り完了 ===");
}
}FKScanner_MotionBased.cs(既存、本セッションで仮説A・B/C・Dの検証で読み込み・分析)
20 体並列で姿勢-EE 対応をスキャンし、fk_scan_motion_based.csv に書き出すスキャナー。本セッションでは ee 計算ロジック(midpoint - root.position)、SetJointPositions と Physics.SyncTransforms の順序、WaitForSeconds(0.5f) のタイミング、FindGripperTipBrep の探索パスを分析した。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
/// <summary>
/// FKScanner_MotionBased
/// 確認済みPICK角度を起点にj0とj1をグリッドサーチし、
/// 各点のEndEffector位置をFKで記録するスキャナー。IKは一切使わない。
/// 20体のロボットを並列稼働させ、点を分担処理する。
/// 物理憲法遵守: drive.target のみ更新。stiffness/damping/anchorRotation には触れない。
/// </summary>
public class FKScanner_MotionBased : MonoBehaviour
{
private static readonly float[] BaseAngles = { -46.93f, 102.74f, -43.33f, 3.6f, -60.11f, -30f };
private const float J0Min = -90f;
private const float J0Max = 90f;
private const float J0Step = 5f;
private const float J1RelMin = -30f;
private const float J1RelMax = 30f;
private const float J1RelStep = 5f;
private const int RobotCount = 20;
[Header("Prefab")]
public GameObject mecharmPrefab;
[Header("MotionLog Mode")]
public bool useMotionLogMode = false;
public string motionLogCsvPath = "Assets/Data/motion_log_block.csv";
public string outputMotionBasedCsvPath = "Assets/Data/fk_scan_motion_based.csv";
[System.Serializable]
public class RobotConfig
{
public ArticulationBody rootArticulationBody;
public ArticulationBody[] joints;
public Transform endEffector;
public Transform leftTipBrep;
public Transform rightTipBrep;
}
[Header("Robot Configs (15体分)")]
public RobotConfig[] robots = new RobotConfig[RobotCount];
private List<string[]> _allRows = new List<string[]>();
private int _doneCount = 0;
private bool _isMotionLogMode = false;
void Start()
{
robots = new RobotConfig[RobotCount];
var foundRobots = new List<GameObject>();
for (int r = 1; r <= RobotCount; r++)
{
var go = GameObject.Find($"mecharm270mpi_{r}_0");
if (go != null) foundRobots.Add(go);
else Debug.LogWarning($"[FKScanner_MotionBased] mecharm270mpi_{r}_0 が見つかりません");
}
for (int r = 0; r < foundRobots.Count; r++)
{
var instance = foundRobots[r];
var cfg = new RobotConfig();
foreach (var ab in instance.GetComponentsInChildren<ArticulationBody>())
{
if (ab.isRoot) { cfg.rootArticulationBody = ab; break; }
}
var jointList = new List<ArticulationBody>();
foreach (var ab in instance.GetComponentsInChildren<ArticulationBody>())
if (!ab.isRoot) jointList.Add(ab);
cfg.joints = jointList.GetRange(0, Mathf.Min(6, jointList.Count)).ToArray();
var eeTransform = instance.transform.Find("base/link1/link2/link3/link4/link5/link6/link6_flange");
cfg.endEffector = eeTransform != null
? eeTransform
: cfg.joints[cfg.joints.Length - 1].transform;
cfg.leftTipBrep = FindGripperTipBrep(instance.transform, "left");
cfg.rightTipBrep = FindGripperTipBrep(instance.transform, "right");
if (cfg.leftTipBrep == null || cfg.rightTipBrep == null)
Debug.LogWarning($"[FKScanner_MotionBased] Robot[{r}] Gripper tip Brep not found");
robots[r] = cfg;
}
if (useMotionLogMode)
{
_isMotionLogMode = true;
StartCoroutine(ScanMotionLogMode());
}
else
{
_isMotionLogMode = false;
var grid = BuildGrid();
int chunkSize = Mathf.CeilToInt((float)grid.Count / RobotCount);
for (int r = 0; r < RobotCount; r++)
{
int start = r * chunkSize;
if (start >= grid.Count) break;
int end = Mathf.Min(start + chunkSize, grid.Count);
var chunk = grid.GetRange(start, end - start);
StartCoroutine(ScanRoutine(r, chunk));
}
}
}
private IEnumerator ScanMotionLogMode()
{
var motionPoses = LoadMotionLogCSV();
if (motionPoses.Count == 0) yield break;
var allGrids = new List<float[]>();
foreach (var pose in motionPoses)
{
var grid = BuildMotionBasedGrid(pose);
allGrids.AddRange(grid);
}
int chunkSize = Mathf.CeilToInt((float)allGrids.Count / RobotCount);
for (int r = 0; r < RobotCount; r++)
{
int start = r * chunkSize;
if (start >= allGrids.Count) break;
int end = Mathf.Min(start + chunkSize, allGrids.Count);
var chunk = allGrids.GetRange(start, end - start);
StartCoroutine(ScanMotionRoutine(r, chunk));
}
}
private List<float[]> LoadMotionLogCSV()
{
var poses = new List<float[]>();
string csvPath = Path.Combine(Application.dataPath, "..", motionLogCsvPath);
if (!File.Exists(csvPath)) return poses;
try
{
var lines = File.ReadAllLines(csvPath);
int lineCount = 0;
foreach (var line in lines)
{
if (lineCount == 0) { lineCount++; continue; }
if (string.IsNullOrWhiteSpace(line)) continue;
var cols = line.Split(',');
if (cols.Length < 8) continue;
float[] angles = new float[6];
for (int i = 0; i < 6; i++)
{
if (!float.TryParse(cols[i + 2], out angles[i])) angles[i] = 0f;
}
poses.Add(angles);
lineCount++;
}
}
catch { }
return poses;
}
private List<float[]> BuildMotionBasedGrid(float[] baseAngles)
{
var grid = new List<float[]>();
float[] offsets = { -15f, -10f, -5f, 0f, 5f, 10f, 15f };
for (int i0 = 0; i0 < offsets.Length; i0++)
for (int i1 = 0; i1 < offsets.Length; i1++)
for (int i2 = 0; i2 < offsets.Length; i2++)
for (int i3 = 0; i3 < offsets.Length; i3++)
for (int i4 = 0; i4 < offsets.Length; i4++)
{
float[] angles = new float[6];
angles[0] = baseAngles[0] + offsets[i0];
angles[1] = baseAngles[1] + offsets[i1];
angles[2] = baseAngles[2] + offsets[i2];
angles[3] = baseAngles[3] + offsets[i3];
angles[4] = baseAngles[4] + offsets[i4];
angles[5] = baseAngles[5];
grid.Add(angles);
}
return grid;
}
private List<(float j0, float j1)> BuildGrid()
{
var list = new List<(float, float)>();
for (float j0 = J0Min; j0 <= J0Max + 0.001f; j0 += J0Step)
for (float j1rel = J1RelMin; j1rel <= J1RelMax + 0.001f; j1rel += J1RelStep)
list.Add((j0, BaseAngles[1] + j1rel));
return list;
}
private IEnumerator ScanRoutine(int robotIndex, List<(float j0, float j1)> chunk)
{
var cfg = robots[robotIndex];
if (cfg == null || cfg.rootArticulationBody == null || cfg.joints == null
|| cfg.leftTipBrep == null || cfg.rightTipBrep == null)
{
OnRobotDone();
yield break;
}
var localRows = new List<string[]>();
foreach (var (j0, j1) in chunk)
{
float[] angles = new float[6] { j0, j1, BaseAngles[2], BaseAngles[3], BaseAngles[4], BaseAngles[5] };
var positions = new List<float>();
for (int i = 0; i < 6; i++)
positions.Add(angles[i] * Mathf.Deg2Rad);
cfg.rootArticulationBody.SetJointPositions(positions);
Physics.SyncTransforms();
for (int i = 0; i < 6; i++)
{
if (cfg.joints[i] == null) continue;
var drive = cfg.joints[i].xDrive;
drive.target = angles[i];
cfg.joints[i].xDrive = drive;
cfg.joints[i].SetDriveTarget(ArticulationDriveAxis.X, drive.target);
}
yield return new WaitForSeconds(0.5f);
Vector3 midpoint = (cfg.leftTipBrep.position + cfg.rightTipBrep.position) * 0.5f;
Vector3 ee = midpoint - cfg.rootArticulationBody.transform.position;
localRows.Add(new string[]
{
ee.x.ToString("F6"), ee.y.ToString("F6"), ee.z.ToString("F6"),
angles[0].ToString("F3"), angles[1].ToString("F3"), angles[2].ToString("F3"),
angles[3].ToString("F3"), angles[4].ToString("F3"), angles[5].ToString("F3")
});
}
lock (_allRows) { _allRows.AddRange(localRows); }
OnRobotDone();
}
private IEnumerator ScanMotionRoutine(int robotIndex, List<float[]> chunk)
{
var cfg = robots[robotIndex];
if (cfg == null || cfg.rootArticulationBody == null || cfg.joints == null
|| cfg.leftTipBrep == null || cfg.rightTipBrep == null)
{
OnRobotDone();
yield break;
}
var localRows = new List<string[]>();
foreach (var angles in chunk)
{
var positions = new List<float>();
for (int i = 0; i < 6; i++)
positions.Add(angles[i] * Mathf.Deg2Rad);
cfg.rootArticulationBody.SetJointPositions(positions);
Physics.SyncTransforms();
for (int i = 0; i < 6; i++)
{
if (cfg.joints[i] == null) continue;
var drive = cfg.joints[i].xDrive;
drive.target = angles[i];
cfg.joints[i].xDrive = drive;
cfg.joints[i].SetDriveTarget(ArticulationDriveAxis.X, drive.target);
}
yield return new WaitForSeconds(0.5f);
Vector3 midpoint = (cfg.leftTipBrep.position + cfg.rightTipBrep.position) * 0.5f;
Vector3 ee = midpoint - cfg.rootArticulationBody.transform.position;
localRows.Add(new string[]
{
ee.x.ToString("F6"), ee.y.ToString("F6"), ee.z.ToString("F6"),
angles[0].ToString("F3"), angles[1].ToString("F3"), angles[2].ToString("F3"),
angles[3].ToString("F3"), angles[4].ToString("F3"), angles[5].ToString("F3")
});
}
lock (_allRows) { _allRows.AddRange(localRows); }
OnRobotDone();
}
private void OnRobotDone()
{
_doneCount++;
if (_doneCount >= RobotCount)
StartCoroutine(WriteCSVAndQuit());
}
private IEnumerator WriteCSVAndQuit()
{
yield return null;
string logsDir = Path.Combine(Application.dataPath, "..", "Logs");
Directory.CreateDirectory(logsDir);
string csvPath;
if (_isMotionLogMode)
{
csvPath = Path.Combine(Application.dataPath, "..", outputMotionBasedCsvPath);
Directory.CreateDirectory(Path.GetDirectoryName(csvPath));
}
else
{
csvPath = Path.Combine(logsDir, "fk_scan_table.csv");
}
using (var writer = new StreamWriter(csvPath, false) { AutoFlush = true })
{
writer.WriteLine("ee_x,ee_y,ee_z,j0,j1,j2,j3,j4,j5");
lock (_allRows)
{
foreach (var row in _allRows)
writer.WriteLine(string.Join(",", row));
}
}
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#endif
}
Transform FindGripperTipBrep(Transform root, string side)
{
Transform g = FindDeepChild(root, $"gripper_{side}1");
if (g == null) return null;
Transform i = FindDeepChild(g, "Instance-Def-210103_4302_101");
if (i == null) return null;
return i.Find("Brep");
}
Transform FindDeepChild(Transform parent, string name)
{
foreach (Transform child in parent)
{
if (child.name == name) return child;
var result = FindDeepChild(child, name);
if (result != null) return result;
}
return null;
}
}仮説E検証コマンド(本セッションで生成・実行)
Mac側CSVとUnity側CSVのファイル更新日時、行数、同angles行の値を比較する一連のbashコマンド。
# 1. 両 CSV のファイル情報
ls -la ~/r2r2r-scripts/fk_scan_motion_based.csv
ls -la ~/Robotics_UX/Robotics_UX_New/Assets/Data/fk_scan_motion_based.csv
# 2. 行数とヘッダ
echo "=== Mac 側 ==="
head -5 ~/r2r2r-scripts/fk_scan_motion_based.csv
wc -l ~/r2r2r-scripts/fk_scan_motion_based.csv
echo "=== Unity 側 ==="
head -5 ~/Robotics_UX/Robotics_UX_New/Assets/Data/fk_scan_motion_based.csv
wc -l ~/Robotics_UX/Robotics_UX_New/Assets/Data/fk_scan_motion_based.csv
# 3. 同 angles の行を両 CSV から抽出
grep "^[^,]*,[^,]*,[^,]*,-50.000,100.000,-55.000,-2.000,-35.000,-20.000" \
~/r2r2r-scripts/fk_scan_motion_based.csv
grep "^[^,]*,[^,]*,[^,]*,-50.000,100.000,-55.000,-2.000,-35.000,-20.000" \
~/Robotics_UX/Robotics_UX_New/Assets/Data/fk_scan_motion_based.csvUnity側 Assets/Data/ 一覧確認コマンド
LOG158 統合テーブルの構成(4ファイル+motion_log_block.csv)を確認するため、ディレクトリ全件のヘッダと行数を出力する。
ls -la ~/Robotics_UX/Robotics_UX_New/Assets/Data/
# 各 CSV の行数とヘッダ
for f in ~/Robotics_UX/Robotics_UX_New/Assets/Data/*.csv; do
echo "=== $f ==="
wc -l "$f"
head -1 "$f"
echo ""
done案A実行コマンド(バックアップ + コピー + 検証)
# 1. 旧 Mac 側 CSV をタイムスタンプ付きでバックアップ
cp ~/r2r2r-scripts/fk_scan_motion_based.csv \
~/r2r2r-scripts/fk_scan_motion_based.csv.bak_DAY170_pre_overwrite_$(date +%Y%m%d_%H%M%S)
# 2. バックアップファイル確認
ls -la ~/r2r2r-scripts/fk_scan_motion_based.csv*
# 3. Unity 側 CSV を Mac 側にコピー(上書き)
cp ~/Robotics_UX/Robotics_UX_New/Assets/Data/fk_scan_motion_based.csv \
~/r2r2r-scripts/fk_scan_motion_based.csv
# 4. コピー後の Mac 側 CSV を確認
ls -la ~/r2r2r-scripts/fk_scan_motion_based.csv
wc -l ~/r2r2r-scripts/fk_scan_motion_based.csv
head -2 ~/r2r2r-scripts/fk_scan_motion_based.csv
# 5. 期待値との一致確認(angles=[-50,100,-55,-2,-35,-20] の行)
grep "^[^,]*,[^,]*,[^,]*,-50.000,100.000,-55.000,-2.000,-35.000,-20.000" \
~/r2r2r-scripts/fk_scan_motion_based.csv
# 期待: ee = (0.252898, 0.018800, 0.219693)効果検証スクリプト(前セッションで生成、本セッションで再実行)
verify_lookup_day169.py は前セッションで作成済み。本セッションでは案A 実行後の Mac 側 CSV に対して再実行することで効果を測定した(XZ最近傍 71.10mm → 0.30mm の確認)。
cd ~/r2r2r-scripts && python3 verify_lookup_day169.pyスクリプト本体は ~/r2r2r-scripts/verify_lookup_day169.py を参照。赤ブロック中心 (0.2401, 0.012, 0.214) を目標座標として、CSV 全件から XZ 距離・3D 距離それぞれの最近傍 top5 を出力する。
