TVTK開発ガイド

作者:Prabhu Ramachandran
連絡先:prabhu_r@users.sourceforge.net
Copyright:2004-2020, Enthought, Inc.

はじめに

このドキュメントでは,TVTKがどのように動作し,どのように実装されるかについての詳細を説明します.tvtkのインストール,機能,使用法に関する基本的な文書は,このファイルと同じディレクトリにあるREADME.txtファイルにあります.

tvtkの動作

すべてのtvtkクラスは TVTKBase から派生します.これは tvtk_base.py で定義されます. TVTKBase は,すべてのtvtkクラスに共通の基本機能を提供します.最も重要なのは,次の機能を提供することです.

  • 既存のVTKオブジェクトをラップしたり,新しいVTKオブジェクトを作成したりできます. TVTKBase.__init__ は,既存のVTKオブジェクトをラップするために使用することができるオプションのVTKオブジェクトを受け入れます.詳細はメソッドdocstringを読んでください.
  • 基礎となるVTKオブジェクトに加えられた変更を伺うためのイベントハンドラを設定します.
  • オブジェクトを選択するための基本的なサポート.
  • update_traits は,基礎となるVTKオブジェクトの状態を反映するように,tvtkクラスの特性を自動的に更新します.
  • traitが変更されたときに,その背後にあるVTKオブジェクトを変更するための共通コード.

前述のように,tvtkクラスは既存のVTKオブジェクトをラップすることも,新しいVTKオブジェクトを作成して新しいオブジェクトを管理することもできます.

traitの自動更新

tvtkオブジェクトの非常に重要な特徴の1つは,オブジェクトのVTKに関連する特徴が,その背後にあるVTKオブジェクトが変更されると動的に更新されることです.これは, 'ModifiedEvent' が呼び出されたときに TVTKBase.update_traits を効果的に呼び出すVTKオブジェクトにオブザーバを追加することによって達成される.VTKオブザーバコールバックの例を次に示します.

>>> import vtk
>>> p = vtk.vtkProperty()
>>> def cb(obj, evt):
...     print obj.__class__.__name__, evt
...
>>> p.AddObserver("ModifiedEvent", cb)
1
>>> p.SetRepresentationToWireframe()
vtkOpenGLProperty ModifiedEvent

このように,プロパティが変更されると,コールバックが呼び出されます.このアプローチの問題点は, pcb への参照を保持していることです.したがって, cb がVTKオブジェクトを管理するtvtkクラスの update_traits メソッドである場合,ガベージ収集不能な参照サイクルの問題と大量のメモリリークが発生します.この問題を回避するには, messenger.py が提供する機能を使用します.TVTKBase.__init__ は実質的に,VTKオブジェクトから呼び出されたときに self.update_traits を呼び出すようメッセンジャーに指示します.Messenger自体は weakrefs を使用するため,参照サイクルの問題はありません.VTKオブジェクトは基本的に messenger.send を呼び出し,残りは messenger が処理します.これにより,tvtkクラスを安全に使用することができ,traitを自動的に更新することもできます.

各tvtkクラスには _updateable_traits_ という属性があります.これはタプルのタプルです.各タプルには以下のような文字列のペアが含まれています:

>>> p = tvtk.Property()
>>> p._updateable_traits_[:4]
(('opacity', 'GetOpacity'),
 ('frontface_culling', 'GetFrontfaceCulling'),
 ('point_size', 'GetPointSize'),
 ('specular_color', 'GetSpecularColor'))

最初の文字列はtraitの名前で,2番目の文字列はVTKオブジェクトからtraitの値を取得するために使用される関数の名前です. TVTKBase.update_traits はこのタプルを使用し,それぞれの特性について,関連するVTK 'get' 関数を呼び出し,特性の値を更新します.

​このプロセス全体は効率的ではないように見えますが,実際には非常にうまく機能し,まったく遅くはないように見えます.

メッセージング

メッセージング機能は tvtk/messenger.py で定義されます.ユニットテストは 'tests/' ディレクトリにあります.前のセクションで説明したように,tvtkオブジェクトはメッセンジャー機能を使用して,traitが常に最新であることを保証します. messenger.py は十分に文書化され,テストされています.詳細はdocstringsを参照してください.ここでは,ドキュメントからの基本的な情報を示します.

messenger.py はシンプルで堅牢で安全な Messenger クラスを実装しており,シグナル/スロット(またはイベント/ハンドラ)のようなメッセージングシステム用のコールバックを登録できます.基本的には,オブジェクトが特定のイベントを送信するときにコールされるコールバック関数/メソッドを登録できます.MessengerクラスはBorgです.したがって,インスタンス化と使用は簡単です.このモジュールはリロードに対しても安全なので,モジュールをリロードしてもコールバック情報は失われません.メソッド・コールバックには,弱参照が使用されるため,参照カウントの問題はありません.主な機能は connectdisconnectsend の3つである.

connect は基本的に,イベント event を発生させるオブジェクト obj をコールバック関数 callback に接続することを許可します.

オブジェクトが send を呼び出す時,送出したいイベントをオプションの引数とキーワード引数と一緒に渡します.特定のイベント用に登録されたすべての callback は,それを呼び出したオブジェクト ( obj ) ,イベント ( event ) ,およびオプションの引数とともに呼び出されます.

disconnect では,特定のコールバック,特定のイベントまたはオブジェクト全体のすべてのコールバックのいずれかを切断できます.

このように,メッセンジャー・クラスは,オブジェクト間通信を容易にする単純なオブザーバー・パターン実装を提供します.詳細については,コードとdocstringを参照してください. tests/test_messenger.py のテストスイートも見てください.

ピックリング

TVTKBase は酸洗いの基本機能を提供します.基本的に,tvtkオブジェクトの辞書 ( self.__dict__ ) はpickle化されます.VTKオブジェクトはピック可能ではないため, _vtk_obj 属性はピックされません.その他の無関係なアトリビュートも除去されます. __setstate__ は, self._vtk_obj が存在するかどうかをチェックすることによって,ライブオブジェクトに対しても機能します.一部のtvtkクラスは,これらのメソッドをオーバーライドして,より適切に実装します.例えば, Matrix4x4 クラスのコードを見てください.

配列の処理

TVTKを使用すると, DataArrayCellArrayPoints などのクラスの代わりに数値配列/Pythonリストを渡すことができます.これは,ラップされたVTKメソッドの呼び出しシグニチャーを見つけ,それが配列を持っているかどうかをチェックし,シグニチャー情報を使用して適切なVTK配列データをインテリジェントに生成することによって行われます.この生成されたVTKデータ配列は,ラップされた関数に渡されます.この機能は,メソッドに複数の呼び出しシグネチャ(過負荷VTK法)があり,そのいずれかに配列が含まれる場合にも機能します.この変換マジックを行う主な関数は, array_handler.py モジュールにあります. wrapper_gen.py モジュールは,呼び出しシグニチャーに応じて適切なラッパー・コードを生成します.したがって,メソッドのシグネチャにVTKオブジェクトが含まれていない場合,ラッピングは必要ありません.配列以外のVTKオブジェクトしか持っていない場合,配列の処理は不要ですが,渡されたtvtkラッパーオブジェクトからVTKオブジェクトを間接参照しなければなりません.呼び出しシグネチャにVTK配列オブジェクトが含まれる場合,数値配列またはPythonリストは正しいVTKデータ配列に変換され,ラップされたVTKオブジェクトに渡されます.

CellArray オブジェクトへの効率的な変換のために,拡張モジュールが必要です.この拡張モジュールのソースは src/array_ext.* にあります.Cython_fileは src/array_ext.c ファイルを生成するために以下のように使うことができます.

$ cd src
$ cython array_ext.pyx

ソースには生成されたソース・ファイルが付属しているため,Cythonを使用していないユーザーでもソース・ファイルを構築できます.

special_gen.py は, DataArray と,これらのオブジェクトが反復, __repr____getitem__ などをサポートすることを可能にする他のクラスのためのすべての追加のメソッドを定義します.

魔法の tvtk インスタンス

次のような場合:

>>> from tvtk.api import tvtk

インポートされた tvtk は,実際にはクラスのインスタンスであり,モジュールではありません.これは,tvtkクラスの数がほぼ800であり,VTK API 全体 をラップしているためです.VTK自体では,定義されたすべてのクラスを一度にインポートすることができます.つまり, import vtk を行うと,すべてのVTK関連クラスをインスタンス化することができます.これは非常に便利な機能です.しかし,800個のラッパークラスを一度にインポートすると, 非常に 時間がかかります.したがって, tvtk は問題を回避するための便利なソリューションを提供します. tvtk はクラス・インスタンスですが,モジュールの場合と同様に動作します.

  • クラス名のタブ補完をサポートします.
  • すべてのtvtkクラスへのアクセスを許可します.
  • インポートは非常に速い.インポートが遅くなるのは,実際にはtvtkがVTKをインポートする必要があるためです.したがって,tvtkのインポートはvtk自体のインポートよりもそれほど遅くないはずです.
  • tvtkクラスをオンデマンドで のみ インポートします.

すべてのtvtk関連コードが生成され, tvtk_classes.zip ファイルに保存されます. tvtk インスタンスは,基本的にはTVTKクラスのインスタンスです.これは tvtk_helper.py で定義されています.このファイルはZIPファイル内にあります.TVTKは,ラップされた各VTKクラスに対して,ラッパーtvtkクラスの名前を持つプロパティーを定義します.プロパティの get メソッドは,基本的にZIPファイルから動的にクラスをインポートした後にクラスを返します.これは get_class 関数で行われます.インポートされた各クラスはキャッシュされ( _cache で),キャッシュされたコピーが存在する場合,クラスは再インポートされません.実装では,クラスとその親クラスをインポートする必要があります.これらの親クラスもキャッシュされます.

enthought/tvtk/__init__.pytvtk_classes.zip ファイルを sys.path に追加し,TVTKクラスをインスタンス化して tvtk インスタンスを提供します.

tvtk_classes.zip 内部のファイルは自動的に生成されることに注意してください.

その他の詳細

tvtk_base.py はtvtkで使われるいくつかの重要で頻繁に使われる特性を定義します.すべてのtvtkクラスで使用されます.これは名前のmanglingを行うための便利な関数( get_tvtk_namecamel2enthought )をいくつか提供しているので,VTKメソッドの名前を考え抜かれたスタイル名に変更することができます.このモジュールは関数 deref_vtk も提供しており,これを使うとtvtkオブジェクトから基礎となるVTKオブジェクトを取得することができます.

コード生成

すべてのtvtkクラスは動的に生成されます. code_gen.py はこれを行う主なドライバモジュールです.コード生成作業には,次の手順が含まれます.

  • すべてのVTKクラスをクラス階層にリストしてソートします.これは class_tree.py によって行われます. ClassTree は基本的にすべてのVTKクラスをツリー構造にまとめます.ツリーは "levels" に編成されます.基底を持たないクラスは最下位レベル(root)にあり,上位レベルは基底を持つクラスを表します.ツリー内の各クラスは, Node として表されます.各ノードには,その子および親への参照があります.したがって,1つのノードを指定してクラス階層全体をウォークできます.これはかなり汎用的なコードであり,どのクラス階層でも動作します.その主な機能は,適切な順序でクラスを生成できるようにすることと,クラス階層内のクラスの親と子を簡単に見つけることです.詳細は class_tree.py .UTSLファイルを参照してください.
  • 各VTKクラスのメソッドを解析し,分類します.このステップには,VTKオブジェクトの状態のデフォルト値や,値の有効な範囲などを見つけることも含まれます.この全機能は vtk_parser.py によって提供されます.VTKパーサーモジュールは完全に自己文書化されており,公開インターフェースはかなり単純です. VTKMethodParser は,デフォルトで ClassTree クラスのインスタンスを使用して,その作業の一部を実行します(ただし, ClassTree の使用はオフにすることができます.).
  • 出力コードを正しくフォーマットする (インデント) .これは indenter.Indent クラスによって処理されます. Indent クラスを使用すると,コードが現在のインデントレベルでフォーマットされるように,コード文字列を適切にフォーマットできます.
  • VTK文書を適切にマッサージする.これは indenter.VTKDocMassager によって行われます.
  • tvtkコードの生成.主に code_gen.py が担当.
  • コードのZIPファイルを生成します.これも code_gen.py によって行われます.

code_gen モジュールはコード生成を駆動する TVTKGenerator クラスを定義します.モジュールは wrapper_gen モジュールを使用してラッパークラスを生成します.

wrapper_genWrapperGenerator クラスを定義します.このクラスのインスタンスは, VTKMethodParser (内部 ClassTree インスタンスを使用します.)のインスタンスを作成します. TVTKGenerator はこのクラス・ツリーを使用します.

VTK階層の各クラス(ツリーのルートにあるクラスから開始)に対して,そのクラスが構文解析され, WrapperGenerator が提供する機能を使って適切なtvtkラッパークラス(クラス名とモジュールのファイル名を適切に変更したもの)が生成されます.また,個別の tvtk_helper モジュール( The magical tvtk instance セクションで説明する)も,ラッパーが生成されたクラスと同時に更新されます.これらのクラスはすべて一時ディレクトリにダンプされます.これらのクラスはZIPファイルに入れられます.

tvtk_helper.py コードは HelperGenerator クラスによって生成されます.その他のtvtkクラスは WrapperGenerator クラスによって生成されます.また, 'WrapperGenerator` は SpecialGenerator を利用して,VTKクラスのいくつかに特別なコードを作成します.

TVTKGenerator は他のコードジェネレータを使用し,コード生成プロセス全体を駆動し,ZIPファイルを構築し,オプションでラッパーコードが書かれた一時ディレクトリをクリーンアップします.

次のコマンドを実行してください:

$ python code_gen.py -h

コードジェネレータのオプションのリストを表示します.オプションの中には,開発中に非常に役立つものもあります.たとえば,いくつかのクラスだけのコードを生成するには,次のようにします.

$ python code_gen.py -n -z -o /tmp/tvtk_tmp vtkCollection \
  vtkProperty vtkConeSource

-n オプションは, '/tmp/tvtk_tmp' が削除されないことを保証します. -z はZIPファイルの生成を禁止します.