Springhead Users Manual

13 Python言語との連携

EmbPythonモジュールは,スクリプト言語Pythonとの連携機能を提供します.PythonインタプリタからSpringheadの機能を呼び出したり,SpringheadアプリケーションにPythonインタプリタを組み込んでスクリプティングエンジンとして使用するといった事ができ ます.

EmbPythonモジュールの使用により,Pythonインタプリタ上にSpringhead APIクラスへのインタフェースクラスが提供されます.ユーザはPythonインタフェースクラスを使用してSpringheadの各機能にアクセスします.Pythonインタフェースクラスは内部的 にSpringheadの機能を呼び出し,結果をPythonインタフェースクラスに変換して返します.

13.1 利用法

大きく分けて二通りの利用法を想定しています.

一つは,C++で実装されたSpringheadアプリケーションに対し,Pythonインタプリタを組み込むことです.Springheadアプリケーションの機能の一部をPythonスクリプト記述し,拡張性を高めます.

もう一つは,Pythonインタプリタに対する外部拡張モジュール(Python DLL, pyd)として提供されたSpringheadを利用することで,PythonアプリケーションにSpringheadの機能を組み込む利用法です.

どちらの場合においても,EmbPythonモジュールはPython側からSpringheadの関数を呼び出すためのインタフェースを提供します.関係をFig 13.1に示します.

Fig. 13.1: Python連携とEmbPythonモジュールの位置づけ

13.1.1 環境変数PATHの設定

Springheadの動作は、Springhead2\core\bin\win64フォルダ、およびSpringhead2\dependency\bin\win64フォルダ内のdll群に依存しています。これらのフォルダの絶対パスを環境変数PATHに追 加してください。

SpringheadへのPython組込み

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

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

Pythonインタプリタ組み込みサンプルは src\Samples\EmbPython\PythonSpr にあります. ビルドすると PythonSpr.exe ができます.

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

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

SPRPYTHONPATH環境変数

  

Springheadリリースを展開したフォルダ内のbin\src\Python32\Libへのフルパスを指定します.Python3.2をc:\Python32にインストールしてある場合,C:\Python32\Libでもかまいません.

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

<スクリーンショット>

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

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

マウス 左ドラッグ

  視点変更(回転)

マウス 右ドラッグ

  視点変更(拡大縮小)

スペースキー

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

PythonSprサンプルの遊び方

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

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インタプリタを組み込むためのコードについて紹介します.

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;
}

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

PythonへのSpringhead組込み

PythonのDLLインポート機能を利用してSpringheadをPythonにロードして用いることができます.

Springheadの機能はSpr.pydというDLLファイルにまとめられています.Spr.pydは,bin\win32\Spr.pydまたはbin\win64\Spr.pydとしてSpringheadリリースに含まれて いますが,src\EmbPython\SprPythonDLL.slnをビルドして生成することもできます.

Spr.pydの使い方

Spr.pyd は,Pythonのインストールフォルダ内にあるDLLsフォルダにコピーして用います.

importでロードします.

Python 3.2.2 [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import Spr

Springheadアプリケーションに組み込む場合と違い,ロード時点では何のオブジェクトも生成されていません.まずPHSdkを生成し,次にPHSceneを生成することで,PHSolidが生成できるようになります.

>>> phSdk = Spr.PHSdk.CreateSdk()
>>> phScene = phSdk.CreateScene(Spr.PHSceneDesc())
>>> solid0 = phScene.CreateSolid(Spr.PHSolidDesc())
>>> for i in range(0,10):
...       print(solid0.GetPose().getPos())
...       phScene.Step()
...
Vec3d(0.000,0.000,0.000)
Vec3d(0.000,-0.000,0.000)
Vec3d(0.000,-0.001,0.000)
...(中略)...
Vec3d(0.000,-0.011,0.000)
>>>

APIの呼び出し方はSpringheadアプリケーション組み込みの場合と変わりません. ただし,この状態ではグラフィクス表示が使えないため出力はテキストやファイルに限られます. グラフィクス表示を使うためには,pyopengl等の描画ライブラリと組み合わせるコード を書く必要があります.

応用例

Spr.pydの応用例の一つにSprBlenderがあります.

SprBlenderは,3DCGソフトBlenderにロードすることでSpringheadを使用可能にする拡張機能で,Springhead開発チームによって公式に開発されています.

BlenderはUI機能の大半がPythonで記述されており,公開されたPython APIを通じて各種の機能を利用することができます. そこで,Blender上のPythonでSpr.pydをロードし,Blender上のCGオブジェクトをSpringheadでシミュレーションできるよ うに書かれたPythonスクリプトがSprBlenderです.

詳しくはWebサイト2を参照してください.

2 http://springhead.info/wiki/SprBlender