mechArm 270のアダプティブグリッパーをUnityデジタルツインに組み込んだ
手動仮組みをやめて、URDFから自動インポートした。
💡 Key Takeaway
グリッパーの組み立ては「手作業でやるもの」だと思い込んでいた。ロボットアームと同じURDFが存在した。同じ手順で自動インポートすればよかった。
やったこと | 起きたこと | 判断 |
|---|---|---|
URDFを探した |
| 手動仮組みをやめた |
package://パスをファイル名に書き換えた | URDF Importerで読み込めた | そのまま進んだ |
ArticulationBodyが残っていた | グリッパーが暴走・link6も壊れた | スクリプトで全削除した |
J6のRotationが再生中にズレた | +30°のオフセットが必要だった | MotionLogPlayerで補正した |
GripperControllerを実装した | 全6パーツが連動して開閉した | Open Angle=35で確定した |
手動仮組みをやめた
— URDFがあるなら、使えばいい。
LOG133でグリッパーを手動で仮組みしていた。gripper_base をlink6の子にドラッグして、座標を目視で合わせて、回転をInspectorで調整する。GripperBuilder.cs を書いて、座標値を何度も書き直して。
「もっと簡単に組めないの?ロボットアームのときはお任せしたよ?」
そうだ。ロボットアームは mycobot_description のURDFから自動インポートした。グリッパーも同じパッケージにある。
find ~/mycobot_ros -name "*.urdf" | grep -i gripperあった。mycobot_ros/mycobot_description/urdf/adaptive_gripper/mycobot_adaptive_gripper.urdf
URDFを変換してUnityに渡す
— package:// パスをそのままでは読めない。ファイル名だけにした。
URDFの中身を確認すると、メッシュのパスがこうなっていた。
<mesh filename="package://mycobot_description/urdf/adaptive_gripper/gripper_base.dae"/>Unity URDF ImporterはROSのパッケージ解決機能を持たない。DAEファイルはすでに Assets/Models/gripper/ に置いてあるので、パスをファイル名だけにすればいい。
sed 's|package://mycobot_description/urdf/adaptive_gripper/||g' \
.../mycobot_adaptive_gripper.urdf \
> .../Assets/Models/gripper/adaptive_gripper.urdf確認:
<mesh filename="gripper_base.dae"/>
<mesh filename="gripper_left1.dae"/>
...Projectウィンドウで右クリック → Import Robot from Selected URDF file → Axis Type: Y Axis → Import URDF。
Hierarchyに firefighter(URDFのrobot名)が生成された。gripper にリネームして、link6の子にドラッグした。
ArticulationBodyが暴走した
— インポートしたままでは動けない。
再生すると、グリッパーが全開して実機なら壊れる角度まで動いた。URDFインポート時に付いた UrdfJointRevolute と ArticulationBody が、独自のControllerスクリプトで動いていた。
link6も再生中にRotationが変わり、グリッパーが斜めになった。
2つを同時に解決するために、Editorスクリプトを書いた。
[MenuItem("R2R2R/Remove Gripper ArticulationBodies")]
static void RemoveAll()
{
var gripper = GameObject.Find("gripper");
foreach (var c in gripper.GetComponentsInChildren<UrdfJointRevolute>())
Object.DestroyImmediate(c);
foreach (var c in gripper.GetComponentsInChildren<UrdfJoint>())
Object.DestroyImmediate(c);
foreach (var c in gripper.GetComponentsInChildren<ArticulationBody>())
Object.DestroyImmediate(c);
Debug.Log("Done");
}R2R2R → Remove Gripper ArticulationBodies を実行。エラーなし。
J6に+30°のオフセットが必要だった
— グリッパーを付けて初めてわかったズレがある。
再生中、グリッパーが斜めになる。HOME姿勢では正しく見えるが、link6が動くと破綻する。
再生前のlink6: Rotation X:90 → 再生後: X:59。約30度ズレている。
MotionLogPlayer.cs のCSV読み込み処理でJ6(インデックス5)にオフセットを加えた。
float angle = 0f;
float.TryParse(cols[j + 2], out angle);
if (j == 5) angle += 30f; // グリッパー取り付け座標系の補正
angles[j] = angle;再生した。グリッパーの傾きが直った。
GripperControllerを実装した
— ArticulationBodyを削除したので、開閉は自前で動かす。
URDFのjoint定義を確認した。
<joint name="gripper_controller" type="revolute">
<axis xyz="0 0 1"/>
<limit effort="1000.0" lower="-0.7" upper="0.15"/>
<parent link="gripper_base"/>
<child link="gripper_left3"/>
</joint>mimic jointで他のパーツが連動している。6パーツすべてを GripperController.cs で制御する。
public class GripperController : MonoBehaviour
{
public Transform gripperLeft3, gripperLeft2, gripperLeft1;
public Transform gripperRight3, gripperRight2, gripperRight1;
public float openAngle = 35f;
public float speed = 90f;
private float _currentAngle = 0f;
private float _targetAngle = 0f;
private Quaternion _left3Init, _left2Init, _left1Init;
private Quaternion _right3Init, _right2Init, _right1Init;
void Start()
{
_left3Init = gripperLeft3.localRotation;
_left2Init = gripperLeft2.localRotation;
_left1Init = gripperLeft1.localRotation;
_right3Init = gripperRight3.localRotation;
_right2Init = gripperRight2.localRotation;
_right1Init = gripperRight1.localRotation;
_currentAngle = 0f;
_targetAngle = 0f;
ApplyAngle(0f);
}
public void Open() => _targetAngle = 0f;
public void Close() => _targetAngle = openAngle;
void Update()
{
_currentAngle = Mathf.MoveTowards(_currentAngle, _targetAngle, speed * Time.deltaTime);
ApplyAngle(_currentAngle);
}
void ApplyAngle(float a)
{
gripperLeft3.localRotation = _left3Init * Quaternion.Euler(0, a, 0);
gripperLeft2.localRotation = _left2Init * Quaternion.Euler(0, a, 0);
gripperLeft1.localRotation = _left1Init * Quaternion.Euler(0, -a, 0);
gripperRight3.localRotation = _right3Init * Quaternion.Euler(0, -a, 0);
gripperRight2.localRotation = _right2Init * Quaternion.Euler(0, -a, 0);
gripperRight1.localRotation = _right1Init * Quaternion.Euler(0, a, 0);
}
}MotionLogPlayer.cs からラベルで呼び出す。
if (gripperController != null)
{
if (_labels[i].Contains("GRIPPER_OPEN")) gripperController.Open();
else if (_labels[i].Contains("GRIPPER_CLOSE")) gripperController.Close();
}GRIPPER_OPENで開き、GRIPPER_CLOSEで閉じる。Open Angle=35でほぼ実機通りの開閉量になった。
デジタルツインにグリッパーが生えた
今日まで、UnityのmechArmはアームだけだった。link6の先に何もない状態で、掴む動作をシミュレーションしていた。
今日からグリッパーがある。開いて、閉じる。実機のmechArmと同じ構造が、デジタル空間に揃った。
精度の話をすると——J6のオフセット+30°はキャリブレーションの一例に過ぎない。グリッパーの取り付け座標系は手動調整だし、開閉の動きは実機のAdaptive Gripperの複雑な機構を6パーツの単純な回転で近似している。完全な再現ではない。
でも「精度より体験の成立を優先する」がR2R2Rの設計哲学だ。
デジタルツインが掴む動作をする。それが成立した。
