1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Pybulletサンプル解説】蛇ロボットを生成・操作する【snake.py】

Last updated at Posted at 2025-05-31

Pybullet公式gitリポジトリのサンプルコードを解説するシリーズです(一覧はこちら)。


今回は、snake.pyを解説します。(コードのリンクはこちら

本コードを実行すると、キーボードにより操作可能な蛇ロボットが生成されます。

snake.gif

使用している機能と仕組み

本コードでは以下の機能を使用して、ヘビ型ロボットの運動シミュレーションを実現しています。

createMultiBody によるリンク構成ロボットの生成

createMultiBody 関数を使用して、複数のリンクと関節からなるロボットを生成します。

p.createMultiBody(
    baseMass,
    baseCollisionShapeIndex,
    baseVisualShapeIndex,
    basePosition,
    baseOrientation,
    linkMasses=...,
    linkCollisionShapeIndices=...,
    linkVisualShapeIndices=...,
    linkPositions=...,
    linkOrientations=...,
    linkInertialFramePositions=...,
    linkInertialFrameOrientations=...,
    linkParentIndices=...,
    linkJointTypes=...,
    linkJointAxis=...,
    useMaximalCoordinates=...
)
  • baseMass:ベースリンクの質量(kg)。
  • baseCollisionShapeIndex:ベースリンクの衝突形状のID。
  • baseVisualShapeIndex:ベースリンクの視覚形状のID。
  • basePosition:ベースリンクの初期位置([x, y, z])。
  • baseOrientation:ベースリンクの初期姿勢(クォータニオン [x, y, z, w])。
  • linkMasses:各リンクの質量のリスト。
  • linkCollisionShapeIndices:各リンクの衝突形状のIDのリスト。
  • linkVisualShapeIndices:各リンクの視覚形状のIDのリスト。
  • linkPositions:各リンクの親リンクからの相対位置のリスト。
  • linkOrientations:各リンクの親リンクからの相対姿勢のリスト(クォータニオン)。
  • linkInertialFramePositions:各リンクの慣性フレームの位置のリスト。
  • linkInertialFrameOrientations:各リンクの慣性フレームの姿勢のリスト(クォータニオン)。
  • linkParentIndices:各リンクの親リンクのインデックスのリスト。
  • linkJointTypes:各リンクの関節タイプのリスト(例:p.JOINT_REVOLUTE)。
  • linkJointAxis:各リンクの関節軸のリスト。
  • useMaximalCoordinates:最大座標系を使用するかどうかのフラグ(True または False)。

setJointMotorControl2 による関節駆動制御(POSITION_CONTROL)

setJointMotorControl2 関数を使用して、各関節に目標角度を設定し、モーターで制御します。

p.setJointMotorControl2(
    bodyUniqueId,
    jointIndex,
    controlMode,
    targetPosition,
    force
)
  • bodyUniqueId:対象のボディの一意なID。
  • jointIndex:制御対象の関節のインデックス。
  • controlMode:制御モード(例:p.POSITION_CONTROL)。
  • targetPosition:目標とする関節角度(ラジアン)。
  • force:モーターが出力できる最大トルク。

異方性摩擦の設定

changeDynamics 関数を使用して、各リンクに異方性摩擦を設定します。

p.changeDynamics(
    bodyUniqueId,
    linkIndex,
    lateralFriction=...,
    anisotropicFriction=...
)
  • bodyUniqueId:対象のボディの一意なID。
  • linkIndex:対象のリンクのインデックス(ベースリンクは -1)。
  • lateralFriction:接触面における横方向の摩擦係数。
  • anisotropicFriction:異方性摩擦係数のリスト([x, y, z])。

これにより、進行方向への滑りやすさを変えることで、ヘビの前進運動を実現しています。

コメントをつけたサンプルコード

以下に元コードにコメントを追記した完全版を掲載します。

import pybullet as p
import time
import math
import pybullet_data

# PyBulletとGUIを接続
p.connect(p.GUI)

# 追加のURDFファイルなどのパスを設定
p.setAdditionalSearchPath(pybullet_data.getDataPath())

# 地面を生成
plane = p.createCollisionShape(p.GEOM_PLANE)
p.createMultiBody(0, plane)

# 各リンクを剛体として扱う設定(TrueでMaximalCoordinates使用)
useMaximalCoordinates = True
sphereRadius = 0.25

# リンクに使うボックス形状(ヘビの1節)
colBoxId = p.createCollisionShape(p.GEOM_BOX,
                                  halfExtents=[sphereRadius, sphereRadius, sphereRadius])
mass = 1
visualShapeId = -1

# ヘビの構成要素(36節のリンク)を定義
link_Masses = []
linkCollisionShapeIndices = []
linkVisualShapeIndices = []
linkPositions = []
linkOrientations = []
linkInertialFramePositions = []
linkInertialFrameOrientations = []
indices = []
jointTypes = []
axis = []

# 36個のリンクを作成し、Y方向につなげていく
for i in range(36):
    link_Masses.append(1)
    linkCollisionShapeIndices.append(colBoxId)
    linkVisualShapeIndices.append(-1)
    linkPositions.append([0, sphereRadius * 2.0 + 0.01, 0])  # Y方向にずらす
    linkOrientations.append([0, 0, 0, 1])
    linkInertialFramePositions.append([0, 0, 0])
    linkInertialFrameOrientations.append([0, 0, 0, 1])
    indices.append(i)
    jointTypes.append(p.JOINT_REVOLUTE)  # 回転関節
    axis.append([0, 0, 1])  # Z軸回転

# ベース(最初のリンク)の初期位置と姿勢
basePosition = [0, 0, 1]
baseOrientation = [0, 0, 0, 1]

# ヘビ型ロボット(マルチボディ)を作成
sphereUid = p.createMultiBody(
    mass,
    colBoxId,
    visualShapeId,
    basePosition,
    baseOrientation,
    linkMasses=link_Masses,
    linkCollisionShapeIndices=linkCollisionShapeIndices,
    linkVisualShapeIndices=linkVisualShapeIndices,
    linkPositions=linkPositions,
    linkOrientations=linkOrientations,
    linkInertialFramePositions=linkInertialFramePositions,
    linkInertialFrameOrientations=linkInertialFrameOrientations,
    linkParentIndices=indices,
    linkJointTypes=jointTypes,
    linkJointAxis=axis,
    useMaximalCoordinates=useMaximalCoordinates
)

# 重力を設定
p.setGravity(0, 0, -10)
p.setRealTimeSimulation(0)  # ステップ実行

# 各リンクに異方性摩擦を設定(進行方向への滑りやすさを変える)
anistropicFriction = [1, 0.01, 0.01]
p.changeDynamics(sphereUid, -1, lateralFriction=2, anisotropicFriction=anistropicFriction)
for i in range(p.getNumJoints(sphereUid)):
    p.changeDynamics(sphereUid, i, lateralFriction=2, anisotropicFriction=anistropicFriction)

# 各種定数の初期化
dt = 1. / 240.
m_wavePeriod = 1.5
m_waveLength = 4
m_waveAmplitude = 0.4
m_waveFront = 0.0
m_steering = 0.0
m_segmentLength = sphereRadius * 2.0

# メインループ
while True:
    keys = p.getKeyboardEvents()
    # 左右キーでステアリング操作
    for k, v in keys.items():
        if (k == p.B3G_RIGHT_ARROW and (v & p.KEY_WAS_TRIGGERED)):
            m_steering = -0.2
        if (k == p.B3G_RIGHT_ARROW and (v & p.KEY_WAS_RELEASED)):
            m_steering = 0
        if (k == p.B3G_LEFT_ARROW and (v & p.KEY_WAS_TRIGGERED)):
            m_steering = 0.2
        if (k == p.B3G_LEFT_ARROW and (v & p.KEY_WAS_RELEASED)):
            m_steering = 0

    # 波の振幅スケールを先頭だけ弱くする
    scaleStart = 1.0
    if (m_waveFront < m_segmentLength * 4.0):
        scaleStart = m_waveFront / (m_segmentLength * 4.0)

    # 各関節に波を伝える
    for joint in range(p.getNumJoints(sphereUid)):
        segment = joint
        # 各リンクの波の位相を計算
        phase = (m_waveFront - (segment + 1) * m_segmentLength) / m_waveLength
        phase -= math.floor(phase)
        phase *= math.pi * 2.0
        targetPos = math.sin(phase) * scaleStart * m_waveAmplitude

        # ステアリング効果を適用(片側の波を弱める)
        if (m_steering > 0 and targetPos < 0):
            targetPos *= 1.0 / (1.0 + m_steering)
        if (m_steering < 0 and targetPos > 0):
            targetPos *= 1.0 / (1.0 - m_steering)

        # 関節モーターに目標角度を指令
        p.setJointMotorControl2(
            sphereUid,
            joint,
            p.POSITION_CONTROL,
            targetPosition=targetPos + m_steering,
            force=30
        )

    # 波を時間で前進させる
    m_waveFront += dt / m_wavePeriod * m_waveLength
    p.stepSimulation()
    time.sleep(dt)
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?