簡易な3Dプロット

お仕事でROSのRVizを使い、点群地図を表示させる事があります。

RVizのマウス操作に慣れてくると、 表示アングル調整が素早く出来るようになり 「うまく出来てるなぁ〜」と感心させられます。

まぁそれも、お仕事で使わせてもらえる「パワフルなマシン」の恩恵も多々あるのかと。

CPUはIntel Core i7。 GPUはNVIDIA GeForce GTX 1070。

非力なマシンでは、ROSを入れてRVizを動かす事自体の敷居が高いです。

とは言え、膨大なデータ量の点群地図を扱わなければ、、、 簡単なデータならば、非力なマシンでもこの操作感を再現できるのでは?

という事で、 RVizの右側のTypeがXYOrbit(rviz)の時のマウスドラッグの動作を、 wxPythonを使って試してみました。

グラフィックの表示は、負荷の軽いワイヤーフレームで。

レイトレーシング 2018春まずはワイヤーフレームから で使ってた部品 (ut, mt, v, line, vecs, ax, fcx, lstx) を流用してみました。

これらの部品を使ってワイヤーフレーム用のエンジンwfrm.pyを作り、 wxPythonのアプリ本体p3d.pyから呼び出してます。



index.yaml (JIS) / body.yaml (JIS)


wxPythonインストール

以前に python3対応のメモ でも、 レイトレーシング 2018春えらいこっちゃ で、自宅のpython環境のデフォルトがpython2からpython3に上がってしまった事を書いてましたが、、、

wxPythonはpython2に入れたままで、python3には入れてませんでした。

さて'wxPython mac install'などとググってみると、、、 なにやら、python3でなかなかうまくいかない気配?

wxPythonのサイトのDwonloadを見てみると、macやwindowsは'pip install -U wxPython'とだけ書いてる感じです。

ならばとpip3で試してみると、とりあえず入ってくれました(^_^v

$ pip3 install -U wxPython
    :

$ python
Python 3.6.5 (default, Mar 30 2018, 06:41:49)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import wx
>>> wx.VERSION
(4, 0, 3, '')
>>>


バージョン1

p3d_v1.tgz

$ tar xzf p3d_v1.tgz
$ cd p3d

起動

$ ./p3d.py -fn xxx.yaml

( -fn xxx.yaml を省略すると cube.yaml )

操作

左ボタンによるドラッグ

視点が目標点の回りを回転します。

視点からの目線の表示なので、表示されてる対象が回転してるように見えます。

右ボタンによるドラッグ

マウスを動かすと、視点が目標点に向かって接近したり遠ざかったりします。

ズーム的な表示の変化になります。

中ボタンによるドラッグ

(今時はマウスのホイール押下か左右同時押しかな?)

ドラッグの移動量に応じて、目標点が移動します。

目標点の移動は、視点を中心とした回転移動にしてみました。 (RVizの動きとはちょっと違うかも...)

その他

RVizではGUIのZeroボタンによるアングルのリセットがありますが、 左ボタンによるダブルクリックで、そのようにしてみました。

データ

ワイヤーフレームのデータは、yamlファイルに書いてロードします。

p3d.py の起動時に -fn xxx.yaml で指定します。 省略するとデフォルトはとりあえず cube.yaml

yamlファイルのデータ形式は、ベタな感じで次の通り。

$ cat cube.yaml
ps:
- [-1,-1,-1]
- [ 1,-1,-1]
- [ 1, 1,-1]
- [-1, 1,-1]
- [-1,-1, 1]
- [ 1,-1, 1]
- [ 1, 1, 1]
- [-1, 1, 1]

idxs:
- [0,1,2,3,0,4,5,6,7,4,1,5,0]
- [2,6]
- [3,7]

eye_p: [0,-20,0]
eye_t: [0,0,0]


バージョン2

上からのビューを追加

RVizのでもそうなのですが、 ビューのタイプがXYOrbitのときの中ボタンドラッグでの調整が難しいです。

上から見降ろしたビューのタイプ、TopDownOrthoを追加してみました。

ビュー切替えは、右ボタンのダブルクリックで行ないます。

RVizのようにポップアップメニューにしても良かったのですが、、、 リセットを左ボタンのダブルクリックにしてたので、その習わしで。

v2.patch

$ cat v2.patch | patch -p1

ビューをTopDownOrthoに切替えたり、XYOrbitに戻したりするとき、 内部で保持してる視点の位置は変化させてません。

描画時の座標に変換の直前で、 視点座標系とグローバル座標系の変換行列に細工して、 真上からの視点になるように変更してます。

なので、中ボタンドラッグ以外の、左ボタンドラッグと右ボタンドラッグでは、 ビューのタイプに関わらず同じ処理です。

TopDownOrthoで中ボタンドラッグすると、 視点と目標点ともにXY平面に沿って平行移動します。

本当に上からのアングルになっているのか確認するため、 cube.yamlの立方体の上面に斜線を1つ追加しました。

上からのビューで中ボタンドラッグで移動させてみると、 視点の近く側にある面の方が少しだけ大きく表示されるので、 斜線のある面が上にきてる事が判ります。;-p)

標準入力からロード

YAMLファイル名の指定 -fn xxx.yaml で、 ファイル名'-'を指定すると標準入力からロードするようにしてみました。

合わせて「いわゆる伝統的な図」のYAMLデータを出力するsinc.pyを追加しました。

$ chmod +x sinc.py

$ ./sinc.py | ./p3d.py -fn -

などと実行すると、生成したデータをファイルに落すことなく表示を試せます。


バージョン3

sinc.pyの図を表示すると、処理が重く反応が鈍いです。

レイトレーシング 2018春 の方式そのままに、C言語で実装したサーバにしてみました。

v3.tgz

$ rm -rf p3d

$ tar xzf v3.tgz
$ cd p3d
$ make

バージョン3の変更内容

これまでのバージョンの構成は一旦捨てて、v3.tgz でまとめなおしてます。 流用するソースはmakeでサイトから引っ張ってきます。

(argmnt/ は 中の opt.py しか使ってません ;-p)

Makefile メイクファイル
p3d.py 本体
wfrm.py ワイヤーフレーム処理とpython版のサーバ
wfrm.c ワイヤーフレーム処理とC言語版のサーバ
dl_rt.sh レイトレーシング 2018春 からダウンロードして rt/ を生成するスクリプト
dl_ezyaml.sh 簡易なYAMLパーサ 2018夏 からダウンロードして ezyaml/ を生成するスクリプト
dl_argmnt.sh テキスト配置ツール からダウンロードして argmnt/ を生成するスクリプト
import.sh 自前のpythonパッケージ内の暗黙の相対パスimportを明示的な相対パスimportに書き換えるスクリプト
librt.patch ダウンロードした rt/ に変更を加えるためのパッチ
cube.yaml 立方体のサンプルデータ
sinc.py 伝統の図のデータを生成するプログラム

レイトレーシング 2018春 からの rt/ には librt.patch をあてて修正するようになってます。

rt/Makeile がライブラリを作るようになってなかったのと、 rt/vecs.c に地雷のようにバグが残ってたので修正。

(春の間、よくこの地雷を踏まずにCGをレンダリングしてたものだ...)

サーバの指定

サーバを使わない場合

$ ./p3d.py -fn cube.yaml
$ ./sinc.py | ./p3d.py -fn -

pythonのサーバを起動して使う場合

-srv オプションを付けます

$ ./p3d.py -fn cube.yaml -srv -ka
$ ./sinc.py | ./p3d.py -fn - -srv -ka

C言語で実装したサーバを起動して使う場合

-srv c オプションを付けます

$ cat cube.yaml | ezyaml/blk.py | ./p3d.py -fn - -srv c -ka
$ ./sinc.py | ./p3d.py -fn - -srv c -ka

C言語のサーバからYAMLデータのロードでは、 簡易なYAMLパーサ 2018夏 のライブラリを使っているので、YAMLのブロックスタイルしかロードできません。

YAMLデータを ezyaml/blk.py を使ってブロックスタイルに変換してからロードします。

-ka オプションを追加すると、クローズボックスをクリックして終了するときに、 rt/val.py の値サーバも含めて、起動した全てのサーバを終了させます。

-ka オプションを指定しない場合は、終了時にwfrmサーバだけ終了させます。

手持ちのMacの環境では、 sinc.pyの図の表示がキビキビと動いてくれるようになりました。v^_^)

pythonのモジュールのインポート

これまでpython2で普通に自前のモジュールをインポートしてました。 いわゆる「暗黙の相対パスのインポート」です。

$ ls
foo.py
bar.py

$ cat foo.py
import bar
  :

カレントディレクトリで実行する分にはええのですが、 例えば今回のように、過去のソース流用するためにパッケージディレクトリを作って置いた場合...

$ find . -type f
./pkg1/foo.py
./pkg1/bar.py
./pkg1/__init__.py
./hoge.py

$ cat hoge.py
from pkg1 import foo
  :

$ cat pkg1/foo.py
import bar
  :

$ python2 hoge.py
  :
$

python2ではこれでOK。

これがpython3では

$ python3 hoge.py
Traceback (most recent call last):
  File "hoge.py", line 1, in <module>
    from pkg1 import foo
  File "pkg1/foo.py", line 1, in <module>
    import bar

python3では、明示的に相対パスのimportにせねばならんとな。

$ cat pkg1/foo.py
from . import bar
  :

$ python3 hoge.py
  :
$

$ python2 hoge.py
  :
$

などと pkg1/foo.py を書き換えると、python3でもOK。

import.sh を作って rt/ の下の自前モジュールの import を書き換えるようにしてみました。

しかしながら、この明示的な相対パスのimportを使ってしまうと、 pkg1/foo.py が "__main__" をもってて実行したい場合...

$ cat pk1/foo.py
from . import bar
  :
if __name__ == "__main__":
        print('foo')
# EOF

$ python2 pkg1/foo.py
Traceback (most recent call last):
  File "pkg1/foo.py", line 1, in <module>
    from . import bar
ValueError: Attempted relative import in non-package

$ python3 pkg1/foo.py
Traceback (most recent call last):
  File "pkg1/foo.py", line 1, in <module>
    from . import bar
SystemError: Parent module '' not loaded, cannot perform relative import

python2 でも python3 でもエラー。

pythonコマンドの -m オプションでモジュールを指定して実行すればOK。

$ python2 -m pkg1.foo
foo

$ python3 -m pkg1.foo
foo

rt/srvs.py から val.py サーバを起動して、他のサーバの起動コマンドを登録したりしとります。

wfrm.py の 関数 new()から srvs.py の infs[] を変更、追加して対応してみました。

(pythonは何でもpublicなので可能ですね ;-p)

TODO


バージョン4

v4.patch

$ make clean
$ cat v4.patch | patch -p1
$ make

TODOの項目対策、対応してみました。

C言語で実装したサーバのとき上からのViewで中ボタンdragの動きが怪しい?

単純なバグでした。 Viewによる中ボタンdragの動作が、pythonのときとは逆になってました。

make cleanしてmakeしなおすとimport.shが何度もあたるのでダメ

import.shの中で org/ ディレクトリの有無で実施済が判定するようにしてみました。

座標変換行列の合成最適化がrt/lstx.pyにあるがrt/lstx.cに実装してなかった

rt/lst.[ch] への lstx_opt() 追加を、librt.patch に含めてみました。 さほど速度の効果がないかも知れませんが、 wfrm.c の get_lst() から lstx_opt() を呼び出すようにしてみました。

背景色は黒の方がしっくりこないか?

背景色のデフォルトを黒くしました。 p3d.py の起動オプション -bg xxx で変更可能です。

$ ./p3d.py -bg wx.WHITE

$ ./p3d.py -bg '(0,128,128)'

などと指定できます。 (括弧の中の数値は r, g, b 順です)

線の色を変えて表示したい

cube.yaml のキー'idxs'の箇所に指定を追加してみました。

$ cat cube.yaml
ps:
- [-1,-1,-1]
- [ 1,-1,-1]
- [ 1, 1,-1]
- [-1, 1,-1]
- [-1,-1, 1]
- [ 1,-1, 1]
- [ 1, 1, 1]
- [-1, 1, 1]

idxs:
- [0,1,2,3,0,4,5,6,7,4,1,5,0]
- col: [200,0,0]
- [2,6]
- [3,7]
- col: [50,200,0]
- [4,6]

eye_p: [0,-20,0]
eye_t: [0,0,0]

idxs のリストの要素として、 キー'col'で、値が [r, g, b] の順のリストを指定します。 (残念ながら 'wx.BLACK' などの文字列は指定できません)

以降、その指定色で線が描画されます。

初めのデフォルト色は白です。

初めのデフォルト色は p3d.py の起動オプション -fg xxx で変更可能です。

$ ./p3d.py -fg wx.RED

$ ./p3d.py -fg '(0,128,128)'

などと指定できます。 (括弧の中の数値は r, g, b 順です)

sinc.pyも無意味に色つきデータを出力するよう変更してみました。;-p)

TODO