SpringheadへのPython組込み

SpringheadアプリケーションにPythonインタプリタを組み込んで利用する方法を解説します. 本節ではまずSpringheadに同梱されたPythonインタプリタ組み込みサンプルを紹介し,簡単な使い方を説明します.その後,サンプルにおけるPythonインタプリタ組み込みのためのソースコードについて解説します.

PythonSprサンプルのビルドと実行

Pythonインタプリタ組み込みサンプルは url textsrc にあります. ビルドすると url textPythonSpr.exe ができます.

PythonSprサンプルは標準的なSpringheadサンプルアプリケーションフレームワークにPythonインタプリタを組み込んだもので,物理シーンを構築・シミュレーション・描画する事ができます.PythonインタプリタからはphSdkfwSdkにアクセスすることができ,表示機能を切り替えたりシーンにオブジェクトを作成したりといったことがPythonから行えます.

実行の前に,環境変数を設定します.これは,Springheadアプリケーションに組み込まれたPythonインタプリタがPythonの標準ライブラリ群にアクセスするために必要です.

SPRPYTHONPATH環境変数

 

Springheadリリースを展開したフォルダ内のurl textbin32へのフルパスを指定します.Python3.2をurl textc:32にインストールしてある場合,url textC:32でもかまいません.

url textPythonSpr.exeを実行すると次のような画面が現れます.

<スクリーンショット>

右がSpringheadの実行画面,左のコンソールがPythonプロンプトです.起動時には,Springhead実行画面には何のシーンも構築されていないため,ワールド座標系を示す矢印のみが描画されています.

操作法は以下の通りです.

マウス 左ドラッグ

視点変更(回転)

マウス 右ドラッグ

視点変更(拡大縮小)

スペースキー

シミュレーション開始・一時停止(起動直後は停止しています)

PythonSprサンプルの遊び方

この節では,Pythonコードを中心としてSpringheadの機能を利用する具体的な方法を紹介します.PythonからのSpringhead API利用に関する詳しい仕様はpythonsprAPIを参照してください.

Pythonプロンプト上にSpringheadのコードを入力して実行することができます.以下のように入力してシミュレーションを開始(スペースキー)すると,剛体が作成されて落ちていきます.

# 剛体が落ちるだけのサンプル

>>> fwScene   ← 初期状態で定義されている変数で,アプリケーションが保持するfwSceneにアクセスできます
<Framework.FWScene object at 0x05250A40>
>>> phScene = fwScene.GetPHScene()
>>> desc = Spr.PHSolidDesc()
>>> desc.mass = 2.0
>>> solid0 = phScene.CreateSolid(desc)

形状を与えることもできます.なお,最後の行のsolid0.AddShape(box0)を実行するまで剛体に形状は割り当てられないので,この行を入力し終わるまではスペースキーを押さずにシミュレーションを一時停止状態にしておくとよいでしょう.

# 形状のある剛体が落ちるだけのサンプル

>>> phScene = fwScene.GetPHScene()
>>> phSdk   = phScene.GetSdk()
>>> descSolid = Spr.PHSolidDesc()
>>> solid0 = phScene.CreateSolid(descSolid)
>>> descBox = Spr.CDBoxDesc()
>>> descBox.boxsize = Spr.Vec3f(1,2,3)
>>> box0 = phSdk.CreateShape(Spr.CDBox.GetIfInfoStatic(), descBox)
>>> solid0.AddShape(box0)

床(位置が固定された剛体)を作成すると,さらにそれらしくなります.

>>> phScene = fwScene.GetPHScene()
>>> phSdk   = phScene.GetSdk()

# 床をつくる
>>> descSolid = Spr.PHSolidDesc()
>>> solid0 = phScene.CreateSolid(descSolid)
>>> descBox = Spr.CDBoxDesc()
>>> descBox.boxsize = Spr.Vec3f(10,2,10)
>>> boxifinfo = Spr.CDBox.GetIfInfoStatic()
>>> solid0.AddShape(phSdk.CreateShape(boxifinfo, descBox))
>>> solid0.SetFramePosition(Spr.Vec3d(0,-1,0))
>>> solid0.SetDynamical(False)

# 床の上に箱をつくって載せる
>>> solid1 = phScene.CreateSolid(descSolid)
>>> descBox.boxsize = Spr.Vec3f(1,1,1)
>>> boxifinfo = Spr.CDBox.GetIfInfoStatic()
>>> solid1.AddShape(phSdk.CreateShape(boxifinfo, descBox))

力を加えることもできます.

>>> solid1.AddForce(Spr.Vec3d(0,200,0))

PythonのForやWhileを使って継続して力を加えることもできます.

>>> import time
>>> for i in range(0,100):
>>>     solid1.AddForce(Spr.Vec3d(0,20,0))
>>>     time.sleep(0.01)

応用として,簡単な制御ループを走らせることもできます.

>>> import time
>>> for i in range(0,500):
>>>   y  = solid1.GetPose().getPos().y
>>>   dy = solid1.GetVelocity().y
>>>   kp = 20.0
>>>   kd =  3.0
>>>   solid1.AddForce(Spr.Vec3d(0, (2.0 - y)*kp - dy*kd, 0))
>>>   time.sleep(0.01)

ここまでは剛体のみでしたが,関節も作成できます.

>>> phScene = fwScene.GetPHScene()
>>> phSdk   = phScene.GetSdk()

>>> descSolid = Spr.PHSolidDesc()
>>> solid0 = phScene.CreateSolid(descSolid)
>>> descBox = Spr.CDBoxDesc()
>>> descBox.boxsize = Spr.Vec3f(1,1,1)
>>> boxifinfo = Spr.CDBox.GetIfInfoStatic()
>>> solid0.AddShape(phSdk.CreateShape(boxifinfo, descBox))
>>> solid0.SetDynamical(False)

>>> solid1 = phScene.CreateSolid(descSolid)
>>> solid1.AddShape(phSdk.CreateShape(boxifinfo, descBox))

>>> descJoint = Spr.PHHingeJointDesc()
>>> descJoint.poseSocket = Spr.Posed(1,0,0,0, 0,-1,0)
>>> descJoint.posePlug   = Spr.Posed(1,0,0,0, 0, 1,0)
>>> hingeifinfo = Spr.PHHingeJoint.GetIfInfoStatic()
>>> joint = phScene.CreateJoint(solid0, solid1, hingeifinfo, descJoint)

PythonSpr.exeに引数を与えると,pythonファイルを読み込んで実行することもできます.ここまでに書いた内容を test.py というファイルに書いて保存し,コマンドプロンプトから以下のように実行すると,test.pyに書いた内容が実行されます(スペースキーを押すまでシミュレーションは開始されないことに注意してください).

C:\src\Samples\EmbPython\PythonSpr> Release\PythonSpr.exe test.py
>>>

Pythonインタプリタ組み込みのためのコード例

PythonSprサンプルにおいて,Pythonインタプリタを組み込むためのコードについて紹介します.

tips

Pythonインタプリタ組み込みの詳細を理解するためにはSpringheadだけでなくPythonのC言語APIについて知る必要があります.詳しく知りたい方はPython/C APIリファレンスマニュアル$^{*1}$等も参照してください.

*1 ... http://docs.python.org/py3k/c-api/index.html

PythonSprサンプルにおいて,Python組み込みのためのコードは main.cpp に記述されています. 関連箇所を抜粋して紹介します.

Python組み込み関連の機能を使用するには,EmbPython.h ヘッダをインクルードします.

#include <EmbPython/EmbPython.h>

Pythonインタプリタは,Springheadアプリケーション本体とは異なるスレッドで動作します.物理シミュレーションステップの実行中や描画の最中にPythonがデータを書き換えてしまうことがないよう,排他ロックをかけて保護します.

virtual void OnStep(){
  UTAutoLock critical(EPCriticalSection);
  ...
}
virtual void OnDraw(GRRenderIf* render) {
  UTAutoLock critical(EPCriticalSection);
  ...
}
virtual void OnAction(int menu, int id){
  UTAutoLock critical(EPCriticalSection);
  ...
}

EPCriticalSectionはアプリケーションに一つしか存在しないインスタンスで,EPCriticalSectionによる排他ロックを取得できるのは全アプリケーション中で一つのスコープのみです.PythonからSpringheadの機能が呼び出される際には必ずEPCriticalSectionの取得を待つようになっているので,排他ロックを取得したOnStepの実行中にPythonがSpringheadの機能を実行することはありません1

次に,Pythonインタプリタ初期化用の関数を定義します.

void EPLoopInit(void* arg) {
  PythonSprApp* app = (PythonSprApp*)arg;

  // Pythonでモジュールの使用宣言
  PyRun_SimpleString("import Spr");
        
  // PythonからCの変数にアクセス可能にする準備
  PyObject *m = PyImport_AddModule("__main__");
  PyObject *dict = PyModule_GetDict(m);

  // PythonからfwSceneにアクセス可能にする
  PyObject* pyObj = (PyObject*)newEPFWSceneIf(app->fwScene);
  Py_INCREF(pyObj);
  PyDict_SetItemString(dict, "fwScene", pyObj);

  // Pythonファイルをロードして実行する
  if (app->argc == 2) {
    ostringstream loadfile;
    loadfile << "__mainfilename__ ='";
    loadfile << app->argv[1];
    loadfile << "'";
    PyRun_SimpleString("import codecs");
    PyRun_SimpleString(loadfile.str().c_str());
    PyRun_SimpleString(
      "__mainfile__ = codecs.open(__mainfilename__,'r','utf-8')");
    PyRun_SimpleString(
      "exec(compile( __mainfile__.read() , __mainfilename__, 'exec')"
      ",globals()"
      ",locals())" );
    PyRun_SimpleString("__mainfile__.close()");
  }
}

この関数は関数ポインタの形でインタプリタオブジェクトに渡され,実行開始時にコールバックされます. 中身はPython上でSpringheadを使用可能にするための手続きと,C上の変数をブリッジするためのコード,そして起動時に指定された.pyファイルをロードするコードなどです.

上の例ではapp->fwSceneのみをPythonに渡していますが,他にも受け渡したい変数が複数出てきた場合は,以下のようなマクロが便利でしょう.

#define ACCESS_SPR_FROM_PY(cls, name, obj)           \
{                                                    \
    PyObject* pyObj = (PyObject*)newEP##cls((obj));  \
    Py_INCREF(pyObj);                                \
    PyDict_SetItemString(dict, #name, pyObj);        \
}                                                    \

// 使い方:
// ACCESS_SPR_FROM_PY(型名, Python側での変数名, アクセスする変数)
ACCESS_SPR_FROM_PY(FWSceneIf, fwScene, app->fwScene);

実際のPythonSprサンプルでは,このマクロを用いていくつかの変数をPythonから呼び出せるようにしています.

ループ関数も定義します.これについては変更することは稀でしょう.

void EPLoop(void* arg) {
	PyRun_InteractiveLoop(stdin,"SpringheadPython Console");
}

最後に,main関数内でPythonインタプリタクラスであるEPInterpreterを作成してコールバックを設定し,初期化・実行を行います.

int main(int argc, char *argv[]) {
  app.Init(argc, argv);

  EPInterpreter* interpreter = EPInterpreter::Create();
  interpreter->Initialize();
  interpreter->EPLoopInit = EPLoopInit;
  interpreter->EPLoop = EPLoop;
  interpreter->Run(&app);

  app.StartMainLoop();
  return 0;
}

Footnotes

  1. ナイーブな実装のため少々過剰なロックとなっています.実際の競合リソースに根ざした排他制御ができるよう,将来のバージョンで変更がなされる可能性もあります.