OpenCASCADE6.7.0でレイトレーシングレンダリングを有効にする

OpenCASCADE 6.7.0 ではレイトレーシング方式のレンダリングをサポートしている事を前回紹介しました。今回は、実際にコーディングをして試してみたものを紹介します。

前提

レイトレーシングレンダリングが何ぞや、というのは、この記事を読んでいる方なら既にお分かりだと思いますので割愛します。OCCTにおけるレイトレーシングの設定は、AIS コンテキストでも Viewer でもなく、View に付随しています。レンダリング、つまり見た目の設定なので、当たり前と言えば当たり前ですね。これは逆に言うと、同じViewerにぶら下がっているいくつかの View は、それぞれ別のレンダリング方式を設定することができるという訳です。 また、レイトレーシングをサポートした事により、それまでの方式と区別するためにOCCTでは次のようにモードとして呼称しています。

  • Raytracing Mode レイトレーシング・モード
  • Rasterization Mode ラスタライズ・モード(従来からあるレンダリング方式)

レイトレーシングがサポートされたことによって、従来からあるラスタライズ・モードが使えなくなるわけではありません。バージョン6.7.0以前のコードも(見た目まわりに関しては)レイトレーシング・モードのサポートによって書き換えの必要はなく、そのままで従来方式のレンダリングでの表示が可能です。

使い方

では、実際にコードを見てみましょう。

void OCCViewer::initViewAppearance(bool is_raytracing, bool is_shadow, bool is_antialias, bool is_reflection)
{
    if (is_raytracing) { // Enable ray tracing mode
        view->SetRaytracingMode();
        view->EnableGLLight(Standard_True);
        is_shadow     ? view->EnableRaytracedShadows()      : view->DisableRaytracedShadows();
        is_antialias  ? view->EnableRaytracedAntialiasing() : view->DisableRaytracedAntialiasing();
        is_reflection ? view->EnableRaytracedReflections()  : view->DisableRaytracedReflections();
    }
    else { // OpenGL rasterize rennaring mode
        view->SetRasterizationMode();
    }
    // ...
}

これは私が開発しているナンチャッテCADビューアー「siren」のソースコードの一部です。レイトレーシングレンダリングを有効にするには、新たに用意されたメソッド呼び出すだけで済み、非常に簡単です。 解説しなくとも書いてあるとおりですが…一応ざっくりと紹介しておきます。

V3d_View::SetRaytracingMode() Viewをレイトレーシングモードに設定します。

V3d_View::SetRasterizationMode() Viewを従来(ラスタライズした形状をOpenGLに投げて描画する)方式に設定します。

V3d_View::EnableRaytracedShadows() / DisableRaytracedShadows() レイトレーシングによる影の表現(陰ではない)を有効・無効にします。

V3d_View::EnableRaytracedAntialiasing() / DisableRaytracedAntialiasing() レイトレーシングモードの時、アンチエイリアスをかけたレンダリングを有効・無効にします。

V3d_View::EnableRaytracedReflections() / DisableRaytracedReflections() レイトレーシングモードの時、形状の鏡面反射を有効・無効にします。反射率は AIS_Shape に関連付けされたマテリアル(材質)によって変化します。

これらのメソッドをViewの初期化時に呼んでやれば、レイトレーシングによるレンダリングを行います。すべてのオプションを有効にすると、次のように描写されます。  

occt670-raytracing-sample

球と面を配置して、材質設定を「金」にしただけですが、綺麗ですね!  

やっぱり重い

私の作業PC(NVIDIA NVS 3100M)ではお世辞でも快適とは言えないくらい動作が遅くなってしまいました。とは言うものの、リアルタイムでレイトレーシングレンダリングをしているわけですから、昔のPCに比べれば、対話的に視点を回転できるだけでもびっくりです。弟の作業PC(もっと良いグラボを積んでます)でレイトレーシングモードを有効にしたところ、少ない形状数であれば、ほぼリアルタイムでぐりぐり動いているようです。 思いっきりハードウェアパワーに依存しているところですので、レイトレーシングモードの濫用は避けるべきだと思います。MayaやMAX、Blenderといった3D CGアニメーションソフトも、作業画面とレイトレーシングが有効にできるレンダリング画面は別になっていますし、用途に合わせて使っていければいいと思います。 レイトレーシングモードに絡んで、新しく追加されたGLGSベースのシェーダーとライトについては、次の記事で紹介したいと思います。

OpenCASCADE 6.7.0 のアナウンス

OpenCASCADEの最新バージョン6.7.0が開発者サイトでアナウンスされています。
記事によると、6.7.0では特に visualization が強化されたようで、簡単かつ高速に高品位のレイトレーシング・レンダリングがビューで可能になったとのことです。

現行リリースの最新版である6.6.0までは、正直に言うと、お世辞でも「綺麗な見た目」は提供されていませんでした。OCCT自体、三次元幾何演算部分が中核となっているライブラリですので画面表現については後まわしになっていたのかもしれません。

結果的にCATIARhinocerosといった商用CADに、第一印象(見た目)で劣るように思われがちになっている感触がありましたが、今回の6.7.0では、商用CADにも負けない美しいビューを表現できるようになったようです。

OpenCASCADE 6.7.0 の新機能

レイトレーシングによるフォンシェーディング・鏡面反射のサンプル
レイトレーシングによるフォンシェーディング・鏡面反射のサンプル
光源と陰を正確に演算しているため、小さな溝やエッジまでよりリアルかつ自然に表現が出来ている。 光源と陰を正確に演算しているため、小さな溝やエッジまでよりリアルかつ自然に表現が出来ている。
半透明処理では、重なり合う物体同士がうまく表現できている。 半透明処理では、重なり合う物体同士がうまく表現できている。
金属表現もこのとおり。 金属表現もこのとおり。

記事をかいつまんで要約しますと、

  • phong シェーディングに対応

三角形ピクセルベースの法線補完と高度な照明モデルを使用して、高品質なシェーディングができるようになったらしいです。これまでは、近似などでごまかしながらシェーディングを行なっていたみたいですが、今回はしっかりとポイントライト(点光源)とディレクショナルライト(平行光源)を使って陰が出来るようになったことで、見た目が大幅に向上したとのことです。

  • 鏡面反射と環境マッピングの対応

レンダリング画面の品質を大きく左右する鏡面反射環境マッピングもサポートされたようです。レイトレーシングがまともに実装されて初めて実現できる機能です。完全な鏡を表現しなくとも、プラスチックや塗装された金属にうっすらと映り込む周りの風景は、より直感的に利用者にイメージを伝えることができるようになると思います。

  • アンチエイリアス処理の改善

6.6.0までは、アンチエイリアス処理を有効にすると、アンチエイリアスであるにも関わらずジャギー(物体の境界のギザギザ)が目立つようになっていました。全然美しくない上に、無駄な処理をするわけで全く使っていませんでしたが、今回の改善でまともに使えるようになった模様です。

  • 半透明オブジェクトの表現の改善

これまでは、半透明オブジェクトを重ね合わせると物体の順序がめちゃくちゃに表現されていました。これは、まともにレイトレーシングをしていなかったため、視点から、手前から順に並んでいるはずの物体の位置を上手く表現できていない事が原因です。今回、これも改善されたようで、CADでなくてはならないこの表現も上手く行きそうです。

  • OpenCLでの処理

レイトレーシングを実装するにあたり、画面の描画コストはこれまで以上に大きくなることは明白です。そのため、並列処理を行うOpenCLをサポートして描画速度の改善を図っています。

レイトレーシングを有効にするコーディング

レイトレーシングモードでの描画を有効にするには、従来のコードをほとんど書き換えることなく、簡単にできるようです。
記事では、V3d_View::SetRaytrasingMode() を実行するだけで有効にできると書いてあります。また、レイトレーシング実装に伴なって DRAW コマンドも追加されており、利用方法もソースコードレベルで示されているみたいです。

今のところの制約事項

6.7.0では、レイトレーシングに関する次のいくつかの機能がまだ制限されているとのことですが、次回以降のリリースで順次利用できるようになっていくそうです。

  • テクスチャーマッピングは今のところ未サポート、従来のテクスチャもレイトレーシングには移植されていない。
  • レイトレーシングが使えるのはポリゴン形状のみで、点や曲線はサポートされていません。(つまり、ワイヤフレームなどもレイトレーシングが使えません)ここで言うポリゴン形状とは、平面分割されたポリゴンメッシュではなく、OpenGL 描画時における面、すなわち自由曲面も含まれているものだと思います。
  • 三角形メッシュの一致問題(OpenGLと同様)。描画時におけるポリゴンメッシュ分割による幾何誤差の事だろうと思います。分割精度を上げれば実用範囲において回避できるだろうとの注釈も。
  • CPU(つまり、ソフトウェアレンダリング)とIntelのGPU(Intel HD Graphicsなど)でテストされていません。
  • また、MacOS Xでもテストされてません。ユーザがこれらの環境に関するパッチ(やレポート)を提供することを求めています。
  • レイトレーシングさせる形状は、今のところOpenGLのVBOを利用せずメインメモリ上から生成するため、メモリ使用量が多くなってます。
    ** 2倍から3倍以上のメモリ使用量になってます。2GBのVRAMで、3,000,000ポリゴン程度。

これらの事は次期リリースに期待しつつも、現状だけでも充分に表現力がアップすると思われます。

DirectXのサポート!?

また、本記事とは別にOCCTのDirectXサポートについての議論も記事になっていました。
曰く、

  • OpenGL に加えて Direct3D レンダラを搭載するのは面白そう。
  • ただ、DirectX を大規模な開発をしてサポートするなら、それなりのメリットが必要。
  • Direct3D レンダラは特定のプラットフォームでしか動かない。
  • レンダラが二つになるということは、バグも倍増されてメンテが大変になる。

とのことでした。ここでは、OCCT本体に実装するのではなく、サードパーティ製の visualization ライブラリとして開発ターゲットにするのは良い方法とも述べています。
個人的には、DirectX の需要はまるでないのですが、90年代後半から今まで培われてきた技術があることは否定しません。終焉を迎えつつある技術でも選択肢が多くなることは良いことだと思います。

Draw Test Harness 日本語 Wiki 作りました

OpenCASCADE の Draw Test Harness の(ほぼ自分用の)日本語 Wiki を立ち上げるために、以前自分の個人サイトでも利用していた UseMod Wiki を導入することにしました。

UseMode Wiki は、Perl で書かれたシンプルな Wiki システムで、煩雑な設定が少なく、依存関係も少ないので重宝していました。基本部は wiki.pl に全て詰まっており、設定ファイルも一つだけ。
見た目に凝ったり、いろんなことをしようと思えば MediaWiki なんかが良いのでしょうが、そんなに時間も割きたくない、簡単な Wiki 構文が使えればいい、程度でしたら、なかなかオススメの Wiki エンジンです。

そんな UseMod Wiki ですが、Wiki 内のユーザ登録が上手く動作しませんでした。エラー内容を調べてみると、Cookie がどうもちゃんとやり取りできていない模様。公式のヘルプを読んでいると、どうも Cookie の期限が「2013 年 9 月まで」とハードコーディングされているようで、思いがけないところでひっかかっていたようです。(しかも2009年あたりからメンテナンスもされていないみたい)

晴れてユーザ登録し、古い記事を新しい Wiki に移植している途中、今度は一部の記事テキストで、ロリポの 403 エラーが出るようになりました。まるで投稿ができない。
最初は何が原因だか分からなかったので、記事名を変更してみたり、いろいろと試行錯誤していたのですが、どうもロリポが(勝手に)ウェブアプリケーションファイアウォール(WAF)を有効にしていたようで、そこにPOST内容がひっかかっていたみたいです。
ロリポは例の乗っ取り事件を起こしてから、ディレクトリのフルパスを強引に変更したり、トップがアレだったりと、あまり良いイメージではありません。他のところだったら、「ああ、そうか、気付かなかったなー」程度で終わるんでしょうが、ロリポの場合ちょっとだけアレな気分になります。
さくらのVPSも持っているので、WordPressのDBまわりの整理ができたら、dyama.orgの参照先をロリポからさくらに切り替えようと思ってます。(なかなかWordPressを気合い入れて調べる時間がない…。)
さくらのクラウドも石狩のデータセンターが稼動し始めたころ、ちょっと不具合があったみたいですが、それ以前から利用している信頼性はやっぱり高いと思います。

WordPressと言えば、WP-Markdown というプラグインを試してみました。こちらも、「そんな高度なレイアウトや見た目を求めないから、さくさくドキュメントが書ける書式で…」という理由で入れてみたのですが、過去の非 Markdown で書いた記事の Syntax Highlight と相性が悪いみたいで、掲載しているソースコードが HTML 混在の表示になってしまいました。
ひょっとしたら設定がマズいのかもしれませんが、かえって手間がかかるかもしれないので導入を見送りました。

Markdownで記事を書き、gistにコードを貼って、記事からはそれを参照…というのも考えましたが、やっぱりバックアップや以前の記事の取り扱いを考えるとムリかなぁ…。(いや、でも引っ越しは楽そう。githubが潰れない限り!)

mrubyがやってきた!

OpenCASCADE の勉強のために、仕事とは関係ないところで、自分なりの簡易三次元 CAD ビューアを作成しています。
形状をパラメトリックに定義したり、対話的に制御したり、一連の処理をモジュール化する上で必要になってくるのがマクロ環境。マクロと言ってしまうと、使い勝手の悪いバッチ処理的なイメージがそこはかとなくありますが、強力なインタプリタ環境を組み込みたいと思いました。

アプリケーションを組む上でもトライアンドエラーのデバッグ効率は上がりますし、CAD ソフトにおける純粋な三次元幾何演算をハード的な部分であるとすれば、「ある決まった工業製品の組み立てモデルをシミュレーションする」といったより論理的な、ソフト的な役割のライブラリ化にも役立つと思っています。

さて、OpenCASCADE にはこのブログでも何回か書いたとおり、DRAW TEST HARNESS というコマンド・ドリヴンな対話型デバッグシェルが含まれています。単機能評価だけで言うと、Tk による最低限の UI とシェルっていう組み合わせは別に捨てたもんじゃないのですが、ユーザに提供するマクロ環境として Tcl/tk を選択するのはいささか時代遅れな感じは否めません。シェルスクリプトや Perl、AWK に馴染みのある人ならば、あんまり気にせずに使い込なせると思うんですが、本質とは違うところでの労力を強いるのは極力避けたいところでもあります。

対話的にも動作することができ、モジュール化しても軽快に動いてくれるインタプリタ環境を探していました。
最低限、

  • 基本的な計算(四則演算、数学関数の呼び出し)
  • 変数の保持と解釈
  • 条件式、繰り返し構文

を備えているものだったら何でも良いと思っていました。ほぼ全ての言語にあるようなものばかりですね。もちろん、書き易さやメンテナンスのやり易さも条件のひとつです。

さて、最初に思いついたのは Python です。Python のコーディングは、私自身、累計でも1万行も書いていないと思いますが、欧米製の大手 CAD ソフトでは Python をマクロ環境として組み込むものが増えてきているようです。フランスのパリに Open CASADE の勉強会に行った時に開発メンバーが見せてくれたデモでも、OpenCASCADE の C++ コードを Python から呼んでいたようです。また、2011年から OpenCASCADE のコミュニティ・エディションをメンテナンスしている Thomas Paviot 氏も PythonOCC という Python ラッパーのプロジェクトを立ち上げています。

大手 CAD や OpenCASCADE との連結でも実績のある Python ですが、組み込み関連の情報を漁っていると、 mruby というものを見つけました。

mruby とは、Ruby の開発者 Matz 氏が最近精力的に開発を進めている組み込み向けの Ruby 実装らしいです。本家 CRuby に比べて依存関係が少なく、小さな端末でも動きやすいよう設計されているようで、Vim 関連の記事でよく参考にさせていただいている mattn 氏頻繁にコミットされている事がブログからも伺えます。

私はガチガチの関数手続き型の頭と、シェルスクリプトのようなゆるゆるのシェル展開ありきの頭の2ビットでコーディングをしていたので、Ruby のような「ちょうど良い」言語には向いていないんだろうなーと食わず嫌いをしていて手をつけなかったような気がします。いや、嫌っていたワケではないのですが、あれ楽しい!これ楽しい!と目移りしている間に、いつの間にか、ちゃんとやってみる機会を逃したというのが正しいかもしれません。少なくとも、ちゃんとやりたい言語の一つであった事は間違いありません。

# 余談ですが、Lisp もちゃんと時間をとってやりたいです。Emacs の設定ファイルのために elisp をごちょごちょ書いたような記憶しかありせん。
# オライリーの「初めての Ruby(オーム社刊、Yasugi 著、2008年)」で紹介されている Ruby の系図に、Ruby が Lisp の考え方も引き継いでいるような図があったので楽しみ。

これは好機とばかりに、mruby を試してみることにしました。

Hello, mruby!

mruby を make するのは簡単でした。他所でも紹介されているように、git で clone して make するだけです。Ruby の環境をまともに触るのも初めてでしたので、初めて知ることがたくさんありました。

まず、Makefile は存在しているのですが、Makefile の中で呼ばれているのは ruby minirake というコマンド。make から minirake が呼ばれて、最終的に gcc がコンパイルする形になってます。コンパイルの動作を処理している minirake は全て Ruby で実装してあり、Ruby 初心者が最初に見た時は「?」マークの連続でした。(でも、それほど時間をかけずに読んでいけました。Ruby すごい!)

頭に mini と付いているのは、きっと mruby のための小さな仕組みであり、本家 CRuby では Rake という Ruby のための Make 環境があるんだろうな、とも理解できました。

Ruby には gem というモジュール管理機構があるようです。Perl で言う CPAN のようなものだと思いますが、rake ファイルにターゲットを追記しておくだけで新しい gem を作ることができました。本家 Perl と軽量目的の mruby を比べるとアレかもしれませんが、分かりやすさ・透明性と簡単さは驚きです。(これなら自分もできるかも!と思わせてくれるのにはテンションが上がります)

さてはて、自分のアプリケーションに組み込む場合は Windows 環境で動かさなければならないので、Windows 環境、とりわけ Visual Studio でビルドできるかというのは大きな問題でした。そして、結果から言うと杞憂でした。
Windows では TortoiseGit と MSYSGIT を使っていますので、Windows に Ruby を入れて PATH を通したあと、MSYS 環境に clone して make するだけでした。途中、bison がないぞって怒られたので、bison.exe を入れたらすんなりと mruby.exe(インタプリタ本体)、mirb.exe(mruby.exeに対話シェル機能を入れたもの)、mrbc.exe(Rubyコードをバイトコードにコンパイルするやつ) が出来てしまいました。これらバイナリは libmruby.a をスタティックリンクしているようで、スタンドアロンで動作可能っていう嬉しさ。さすが組み込み向けです。

コーディングをほとんどしていないのに、こんな側面から mruby がどんどん好きになってしまいます。

Visual Studio で

自分のアプリケーションを Windows 以外のプラットフォームでも提供するんだったら、Qt なんかでチャキっと組んだ方がいいと思うんですが、私は Qt を使ったことがありません。Qt はすごくやりたいのですが、それはまた別の機会として、当面の目標である Windows でそこそこ動くものを作らねばなりません。

そういう理由で、ユーザ・フロントエンドを C#、OpenCASCADE や mruby のラッパー DLL を C++/CLI で実装しよう(※)と思いました。ラッパー DLL も C++/CLI 依存の部分とネイティブ C++ 準拠の部分を分けてコーディングする事により、最悪、別のプラットフォームになった場合、ネイティブ C++ の部分だけ抜き出してビルドし直せばいいと思いました。MFC を使った COM 実装に比べれば、C++/CLI の制約なんて目をつぶる事ができそうですし、そんな高度な事もする予定がないので、当面はこれで問題がないはずです。

※Microsoft の公式推薦の C++/CLI 入門書籍でも、「C++マネージド拡張の失敗という前例もあるし、特に理由がなければ C++/CLI なんて使うな」と前書きに書いてありましたね。あれは声出して笑いました。

さて、さっそく MSYS環境で make した mruby.dll を Visual Studio で作成した C++/CLI の wmruby.exe (デバッグの為、とりあえずEXEを作成)からリンクしてみて実行しました。コンパイル、リンクとも正常に完了しているものの、実行時には 0xc0000007b というエラーが出て、実行できません。調べてみると、どうもバイナリイメージが CIL とネイティブの間で上手く取りつげていないようです。

ここは mruby ではなく、MSYS と、Windows の目紛しく仕組みが変わるバイナリの取り扱いの不一致だろうって事で、あまり深く調べずに考え方を変えました。

mruby.dll を make するために rake ファイルを読み漁ってた時に vs2010 や vs2012 といった、いかにも Visual Studio で mruby をビルドするオプションっぽいのを発見していました。

openssl とか、gem が他のオープンソースライブラリを使う時、Visual Studio ではなく MSYS 環境のほうが色々と苦労が少なそうだなーと思っていたので、あえて MSYS で libmruby.a を make していましたが、そこが問題になりそうだったら選択肢がありません。

vs2010 を使って、ビルドし直してみると、Visual Studio によって libmruby.a ではなく libmruby.lib が生成されました。加えて、MSYS でビルドしていた自前の mruby.dll も Visual Studio で C++/CLI として libmruby.lib にリンクして再ビルドすると、拍子抜けするほど上手く行きました。

libmruby.lib さえビルドできてしまったら、あとはこっちのもんですね!

IIJ 版

これまで触っていた本家の mruby では、require やシェルコマンド実行のクオートなど、Kernel でサポートしているものもほとんどありませんでした。開発途上という側面と、最低限のものを提供するというスタンスがあると思われます。

本家 mruby のほか、IIJ さんも精力的に mruby の開発に手を出しているようで、本家から folk したリポジトリになっています。mattn 氏のブログを拝読していたら、先程述べた require やクオート、正規表現など「モバイル端末などの局所的な組み込みには要らなかもしれない!でもPCで実行する対話的アプリのモジュールなら要るよね!」的な機能を IIJ 版にどんどん追加されていっている様子が伺えました。記事にもありましたが、CRuby にも遜色がないほどの基本機能を提供できるようになっているようです。本当に凄いし、夢が広がります!

という事で早速、IIJ 版を clone して make してみました。linux では何ごともなくすんなり。
./mirb して

[ruby]
ls.each_line do |item|
puts item
end
[/ruby]

すると、ちゃんと実行できました。楽しい。

さて、Windows 環境ではどうかと言うと、ソケットを扱ってそうな gem があったり、コードを見てみると Windows 向けのコーディングがされていなかったりと、半ば予想はしていましたが、Visual Studio でビルドしようとしても失敗しました。当たり前だよネ。

Visual Studio っていう巨大なイレギュラー環境が立ちふさがって、作業がストップしてしまいましたが、丹念に乗り越えなければならない道だろうなぁと思います。今はビルド方法をまとめるくらいしか能力がないですが、Ruby をガリガリ使えるようになって、実装の事も理解できるようになったら issue 飛ばしたり、コーディング面でも協力できるようになれればなあ、と思いました。

OpenCASCADE の Draw Test Harness を活用する その3

前回までの記事では、OpenCASCADE の Draw Test Harness を利用し、独自コマンドを組み込むところまでを紹介しました。今回は、実例を用いながらコーディングしていきたいと思います。

目的

任意の半径を持つ球体を 1/8 にした形状を作成し、その容積を印字したいと思います。

想定しているコマンドの書式は次のとおり。

[bash]
sphere1p8 result_name radius_of_sphere
[/bash]

アプローチとしては、まず原点(0, 0, 0)に半径 r の sphere を作り、同じ位置に大きさ(r, r, r) の box を作成し、二つをコモン演算します。その結果に対して容積を取得しようと思います。

ソースコード

まず、前回のサンプルコードをベースに、プリミティブ、ソリッド、プロパティを扱うためのヘッダを追加してあげます。次に、hello() 関数を sphere1p8() 関数に書き換え、コマンド定義も変更してやります。

[cpp]

include <iostream>

include "/opt/occ660/ros/config.h"

include <Draw.hxx>

include <Draw_Interpretor.hxx>

include <DBRep.hxx>

/* コマンドで利用するヘッダ */

include <TopoDS_Solid.hxx>

include <BRepPrimAPI_MakeBox.hxx>

include <BRepPrimAPI_MakeSphere.hxx>

include <BRepAlgoAPI_Common.hxx>

include <BRepGProp.hxx>

include <GProp_GProps.hxx>

include <Standard_PrimitiveTypes.hxx>

/* 1/8球を作成し、ボリュームを印字する */
static int sphere1p8(Draw_Interpretor& di, Standard_Integer argc, const char** argv)
{
if (argc != 3)
return 1;

// 半径
double r = atof(argv[2]);

// 球と箱を作る
BRepPrimAPI_MakeSphere sphere(gp_Pnt(0, 0, 0), r);
BRepPrimAPI_MakeBox box(gp_Pnt(0, 0, 0), r, r, r);

// ブール演算
BRepAlgoAPI_Common bo(box.Solid(), sphere.Solid());
bo.SetOperation(BOPAlgo_COMMON); // 6.6.0 から BOP_* が BOPAlgo_* になってる
bo.Build();

if (bo.ErrorStatus())
    return bo.ErrorStatus();

// 結果を取り出して、登録
TopoDS_Shape shape = bo.Shape();
DBRep::Set(argv[1], shape);

// 容積を印字
GProp_GProps gpr;
BRepGProp::VolumeProperties(shape, gpr);
std::cout &lt;&lt; std::endl &lt;&lt; &quot;Volume: &quot; &lt;&lt; gpr.Mass() &lt;&lt; std::endl;

return 0;

}

void Draw_InitAppli(Draw_Interpretor& di)
{
Draw::Commands(di);
di.Add("sphere1p8", "sphere1p8 result r", FILE, sphere1p8);
}

include <Draw_Main.hxx>

DRAW_MAIN
[/cpp]

sphere1p8() 内の処理は、コメントを参考にすれば説明は不要だと思います。1点だけ、補足をしておきますと、40行目の DBRep::Set() 関数で DBRep という Draw の幾何オブジェクト管理構造にオブジェクトを登録しています。この DBRep に登録する事により、AXO ビューアにオブジェクトが表示されることになります。また、Draw インタプリタ上からオブジェクトの名前をキーにして扱うことも出来るようになります。

Draw 内だけで使われるもので、有用な関数を次に示します。

  • Draw_Interpretor::Eval … 関数内から Draw コマンドを実行する
    例)

    di.Eval("pload ALL; axo");
  • Draw_Interpretor::EvalFile … 関数内からファイルパスを指定し、Tcl スクリプトファイルを実行する
    例)

    di.EvalFile("script.tcl");
  • DBRep::Set … オブジェクト名をキーにして、オブジェクトを管理構造に追加する
    例)

    DBRep::Set(objname, myObject);
  • DBRep::Get … オブジェクト名をキーにして、オブジェクトを管理構造から取得する
    例)

    TopoDS_Shape myObject = DBRep::Get(objname);

上記の例のように Eval() の文字列はコマンド単位ではなく、一連のスクリプトの内容として食わせることが可能です。(コマンドはセミコロン区切りで1行に連結できます。)
また、DBRep はオブジェクトを静的に管理しているようなので、static 呼び出しで使います。 コマンドの引数として指定されたオブジェクト名で、オブジェクトを取得・設定する際に利用します。
これだけ知っていれば充分ですが、さらに詳しい情報は、Draw_InterpretorDBRep のリファレンスを参考にしてください。

ビルドと実行

前回のサンプルファイルの時から、利用しているライブラリファイルが増えましたので、ビルドコマンドライン、とりわけ Makefile も書き換える必要があります。

[cpp]
g++ -I/usr/local/inc -lTKernel -lTKDraw -lTKMath -lTKBRep -lTKTopAlgo -lTKPrim -lTKBO -lTKG2d drawtest.cpp
[/cpp]

沢山増えましたね。リンカエラーで「クラス○○の参照が見つかりません!」と怒られた場合は、クラスのリファレンスマニュアルを参照すると、ライブラリ名を特定することができます。例えば、Draw_interpretor クラスの参照エラーとなった場合、リファレンスページの画面上部に「Open CASCADE Technology > Module Draw > Toolkit TKDraw > Package Draw」と表示されています。この Toolkit の部分がライブラリ名になるので、この場合ですと「TKDraw」というライブラリをリンクしてあげればいい事になります。
OpenCASCADE ライブラリは、ほとんどに接頭辞 TK が付いていますが、Toolkit の略です。Tcl/Tk の Tk とおんなじですね。

ビルドが成功したら、実行バイナリを起動して、pload ALL、axo の順にタイプします。その後、追加したコマンド sphere1p8 obj 10.2、 fit コマンドを実行すると、次のようになりました。

dth_volume_sample容積は、約 555 らしいです。ちゃんと動いている模様。

あとは、コンパイラに -g して GDB などに投げて、ガリガリとトライ&エラーが楽しめます。

OpenCASCADE の Draw Test Harness を活用する その2

前回の記事で、OpenCASCADE に同梱されているコマンド駆動型の効率的なデバッグ環境 Draw Test Harness の使い方を紹介しました。今回は、いよいよデバッグ&プレビュー環境として活躍させるための、独自コマンドを追加する方法を書きたいと思います。

Draw Test Harness に独自コマンドを追加する

Draw Test Harness の環境に自前のコマンド(コード)を追加する方法は簡単です。どちらかと言うと、自分のデバッグしたいコードを取り出して、Tcl ないし Draw インタプリタを組み込むイメージです。何はともあれ、まずはコードを見てみましょう。

[cpp]
// File: drawtest.cpp

include <iostream>

// このプログラムを Draw インタプリタプログラムにするために必要なヘッダ

include "/opt/occ660/ros/config.h"

include <Draw.hxx>

include <Draw_Interpretor.hxx>

include <DBRep.hxx>

static int hello(Draw_Interpretor& di, // 1. コマンド実装部
Standard_Integer argc, const char** argv) //
{ //
std::cout << "hello, draw!" << std::endl; //
return 0; //
} //

void Draw_InitAppli(Draw_Interpretor& di) // 2. コマンド定義部
{ //
Draw::Commands(di); //
di.Add("hello", "print hello string for test", //
FILE, hello, "Test command"); //
} //

include <Draw_Main.hxx> // 3. アプリケーション

DRAW_MAIN // 実行部

[/cpp]

こちらの手順で OpenCASCADE を導入した場合を前提にして書いていますが、5行目の CASROOT ディレクトリ(ros ディレクトリ)の直下にある config.h のパス文字列を、お使いの環境での config.h のパスに変更するだけで正常に動くと思います。

このプログラムは、1. コマンド実装部、2. コマンド定義部、3. アプリケーション実行部に分かれています。一つ一つはすごく簡単なので、ちゃっちゃか解説しちゃいましょう。

1. コマンド実装部

この関数がコマンドの実際の処理となります。上のサンプルでは、文字列を標準出力に印字する hello() 関数として実装しています。Draw インタプリタとコマンドの実行引数を取る静的関数であれば、名前は何でも構いません。複数のコマンドを実装したい場合は、コマンド毎に、コマンド用インタフェイスを持つ関数を追加してあげます。

[cpp]
/* コマンド用インタフェイス */
static int // 戻り値: コマンド正常終了時は0。
funcname (
Draw_Interpretor&, // Draw インタプリタのアドレス
Standard_Integer, // コマンドの引数数
const char** // コマンドの引数文字列
)
[/cpp]

2. コマンド定義部

Draw_InitAppli() はアプリケーションが初期化される際に一度だけ自動的に呼ばれる関数です。ここで、Draw::Commands() を呼び出した後、Draw_Interpretor::Add() 関数を使って、上記で追加した関数とコマンドの関連付けを Draw インタプリタに宣言します。

[cpp]
Draw_Interpretor::Add(
const Standard_CString Command, // コマンド名
const Standard_CString Help, // コマンドのヘルプ文字列
const Standard_CString Filename, // コマンドの関数がコーディングされているファイルパス
const Draw_CommandFunction Function, // コマンドの関数ポインタ
const Standard_CString Group=&quot;User Commands&quot; // コマンドのグループ文字列
)
[/cpp]

コマンド名と関数さえしっかり定義できていれば、あとは適当で構いません。ヘルプ文字列は、Draw インタプリタで help commandname を実行した際に、表示されるテキストを指定します。普通はコマンドの使い方を書いておくためのものです。

3. アプリケーション実行部

DRAW_MAIN 定数により、アプリケーションに必要な main() 関数や Draw インタプリタのその他の機能の呼び出しなど、面倒なコーディングはプリプロセッサが全部やってくれます。最後にこの2行を書いておけば、とりあえずOK。

ビルドと実行

ビルドは次のとおりです。TKDraw ライブラリを忘れずにリンクしましょう。

[bash]
g++ -lTKernel -I/usr/local/inc -lTKDraw drawtest.cpp
[/bash]

ここで生成された a.out を実行し、「hello」とタイプすると、hello() 内で定義したコードが実行されます。※ a.out を実行した際に「the CASROOT variable is mandatory to Run OpenCascade」というメッセージが表示されたら、環境変数 CASROOT が上手く定義されていません。

「hello, draw!」と印字されれば成功です。「exit」 とタイプして終了させましょう。

今回のサンプルでは、hello() 関数内で OpenCASCADE を用いた処理は一切行なっていなかった為、a.out の実行直後に hello コマンドを実行しても大丈夫でしたが、今後、OpenCASCADE を用いた処理を書くようになれば、必ず「pload ALL」を実行しなければなりません。

次回は、もう少し意味のあるデバッグコマンドを実装しながら、コマンド内でよく使うであろう機能を紹介したいと思います。

onnanoko

本日のまとめ

  1. Draw,Draw_Interpretor,DBRep ヘッダを読み込み、Draw_InitAppli() と DRAW_MAIN を持つソースコードを準備する。
  2. コマンド用インタフェイスを持った関数を作り、Draw_InitAppli() 内で、コマンドと関連付けする。
  3. コンパイルし、libTKDraw にリンクして、実行する。

OpenCASCADE の Draw Test Harness を活用する その1

OpenCASCADE には、コマンド駆動型の効率的なデバッグ環境 Draw Test Harness というものが付属しています。 OpenCASCADE のコードを、単機能ごとに評価できるツールとして開発には外せないものであり、非常に有用なものです。快適なデバッグするのには、グラフィカルなユーザインタフェイスやイベント、コマンド実行の仕組み、簡単な繰りかえし処理や変数、数式の解釈など、かなり高次元なフレームワークを構築しないと、難しいものがあります。その「本質以外の部分」を担ってくれるのが Draw Test Harness です。

Draw Test Harness の簡単な説明

Draw Test Harness は Tcl 言語のインタプリタを内包しており、Tcl 言語のモジュールとして OpenCASCADE を利用された様々なコマンドを実行することができます。また、Viewer や簡単なマウス処理も提供されているので、対話的にコマンドを実行し、結果を三次元ビューで確認することができます。

Tcl 言語をご存知ない方にとっては、「今から新しい言語を覚えるのか…」と逃げ腰になる必要はありません。確かに Tcl はレトロな言語なのですが、インタプリタとしては使い古されており、あまり高度なことをしようとしない限り、充分に、クイックリーに動いてくれる環境です。基本的には、デバッグを自動化してくれるマクロという位置付けですので、バッチファイルやシェルスクリプトを書いたことがある方ならば、充分に使いこなせると思います。基本はコマンドの羅列です。

Draw Test Harness では GUI のメニューも提供されていますが、こちらは Tcl 言語の相棒といっても過言ではない GUI フレームワーク Tk で実装されています。こちらに関しては、Tk を用いて GUI を構築しようとしない限り、まったく知識は要りません。

Draw Test Harness を実行する

前項の手順で OpenCASCADE をインストールし、ros ディレクトリを指す環境変数 CASROOT が定義された状態を前提としています。X サーバが動いている端末または、Xを飛ばしたクライアント上から、次のコマンドをタイプします。

[bash] DRAWEXE [/bash]

GUIのメニューが表示され、端末には「Draw[n]>」というプロンプトが表示されたと思います。これが Tcl コンソールです。プロンプトに続けて、

[bash] pload ALL axo box b 10 10 10 fit [/bash]

とタイプしていくと、平行投影ビューにボックスが表示され、ビューにフィットすると思います。dth

1行目の「pload ALL」は、モジュールファイルを全て読み込むコマンドです。このコマンドを最初に実行しておかないと、Tcl コンソールから OpenCASCADE モジュールをコールすることができません。(ちなみに、OpenCASCADE モジュールは、環境変数 CASROOT で定義された ros ディレクトリからパスを追っていき読み込んでいます。) 2行目の「axo」で平行投影ビューを表示して、3行目の「box b 10 10 10」で「(原点位置に)大きさ10,10,10のbというboxを作る」というコマンドです。最後の「fit」コマンドで、平行投影ビューいっぱいにboxの表示をフィットさせています。

最後に、「exit」とタイプすると Tcl コンソールが終了します。

このような具合で、コマンドを対話的にタイプしていって IGES を読み込んだり、オブジェクトの容積を出したりすることができるツールが Draw Test Harness です。 Tcl コンソール自体がインタプリタになっているので、上でタイプしたコマンドをテキストファイル box.tcl に保存しておき、

[bash] DRAWEXE < box.tcl [/bash]

することにより、一連の作業を自動的に処理させることもできます。 次の Tcl スクリプトは、ボックスと平面の交線(エッジを含むコンパウンド)を水平レベルごとに取得し、表示するサンプルです。

[perl]

!/usr/bin/env tclsh

pload ALL

ビューの作成

axo

ボックスの作成

box b 10 10 12 fit

ボックスを傾ける

trotate b 0 0 0 1 0 0 14 trotate b 0 0 0 0 1 0 -21 fit

水平面で交線を求める

for {set i 1} {$i <= 14} {incr i} { plane p$i 0 0 $i 0 0 1 psection c$i b p$i unset p$i } [/perl]

簡単な制御構文を用いています。私自信、Tcl 言語はそれほど書いたことあるわけではないのですが、以前のブログ記事を見てみるとこんなモノも書いていたようです。全然覚えてないな・・・。

Windows 環境では、CASROOT ディレクトリ(ros)直下にある draw.bat をダブルクリックすると、Tcl コンソールが開きます。

Draw Test Harness を使った応用

Draw Test Harness には、OpenCASCADE モジュールとして提供されているコマンドが数百種類もあります。ここでは、一つ一つに関しての説明は省きますが、これらのコマンドを組み合わせることで、ちょっとした非対話的な CAD ないし、三次元データコンバータくらいなら書けてしまいます。また、DRAWEXE の標準入出力をつないだアプリケーションを書けば、コマンド駆動型、かつしっかりとしたGUIを積んだアプリケーションを書けないことはありません。ちょっとした IGES ファイルの形状のチェックや、形状を反転・拡大縮小したい場合などにも力を発揮します。

また、提供されているコマンドの実装はソースコードレベルで公開されていますので、例えば、三次元オブジェクトの重心位置を取得するコードを書きたい場合、Draw コマンド vprops の実装を調べれば良いわけです。Draw インタプリタ内で「getsourcefile vprops」とタイプすると、ソースコードのファイル名を教えてくれます。そのファイル名を基に $CASROOT/ros/src から目的のソースコードを探し出すことができます。Draw コマンドは、基本的には単機能ごとに機能を提供しているものなので、Draw コマンドの実装方法が「最強のサンプル集」となることでしょう。

さらに、Draw Test Harness では、既存のコマンドに加え、ユーザが C++ を用いてコーディングした OpenCASCADE のコードも簡単に呼び出すことができます。OpenCASCADE を使う、つまり C++ で OpenCASCADE を使う場合、Draw Test Harness は最強のデバッグビューアとなるのです。

次回は、Draw Test Harness に自分で書いた OpenCASCADE コードを独自のコマンドとして登録し、実行してみます。

onnanoko本日のまとめ

  • Draw Test Harness は、コマンド駆動型の強力な OpenCASCADE インタプリタ。
  • 面倒な GUI 周りが最初から準備されているので、三次元幾何演算のトライアンドエラー、プレビューがかんたんにできる。
  • 対話的に利用するもよし、数百種類あるコマンドを組み合わせてバッチ処理をさせてもよし。
  • それぞれのコマンドは、source commandname によって C++ のソースコードを参照できる。

OpenCASCADE における幾何オブジェクトの管理

OpenCASCADE で、幾何オブジェクトを管理するにはいくつも方法があるようですが、付属のコマンド実行環境 Draw Test Harness では、幾何オブジェクトに対して文字列の名前を与え、それで管理できる仕組みを提供しています。
具体的には、Standard_CString 文字列をキーとする AIS コンテキストに登録された Shape との関連付けマップ構造を持っているようです。Draw Test Harness 上でコーディングする際、次のようなコードをしょっちゅう書きます。

[cpp]
{
// …

// オブジェクトを取得
TopoDS_Shape S = DBRep::Get(shapename);

// ...

// オブジェクトを追加
TopoDS_Shape SS = ...
DBRep::Set(SS, shapename2);

}
[/cpp]

DBRep クラス
のソースを追ってみると、名前文字列とインデックスキーの関連付けに Tcl のマップが利用されており、その後で Draw クラスの Set(), Get() がコールされており、Draw クラス内で Draw_VMap 型のマップが宣言されていました。さらに、Draw_VMap は TCollection_BasicMap クラスの派生クラスとなっており、このコレクション・マップを利用すれば幾何オブジェクトを管理することができるようです。

誘惑に負けて std::map を使いそうになってしまいますが、特にプログラミング上の制約の多い Windows で幾何オブジェクトを取り扱うには、TCollection_BasicMap を利用するのが無難なようです。

OpenCASCADE 6.6.0 をビルドする。

OpenCASCADE 6.6.0 がリリースされて数ヶ月経ちますが、ようやく自宅の環境でもビルドする暇が出来たので手順を書いておきたいと思います。
手順と言っても、ソースコードを落としてきて make するだけなので、すごく簡単です。

OpenCASCADE 6.6.0 のビルド

うちの環境は相変わらず Debian 7.0 の x86 環境ですので、その環境を前提として書いています。
まず、公式サイトのダウンロードページより、6.6.0 の樽玉を落としてきます。ここでは、/tmp/occ640 を作業ディレクトリにしますので、そこに落とします。ダウンロードの際は、公式サイトにユーザ登録する必要があります。

ダウンロード後、次のコマンドを実行します。

[bash]
cd /tmp/occ640
tar zxvf OpenCASCADE660.tgz
cd ros
./build_configure

configure が生成される

./configure –enable-data –enable-algo –enable-vis –enable-caf –enable-dxe –enable-draw \
–with-tbb-include=/usr/include/tbb –with-tbb-library=/usr/lib –with-freeimage=/usr/include \
–with-gl2ps=/usr/include
[/bash]

configure にビルドするコンポーネントと OpenCASCADE が利用しているライブラリのパスを指定しています。何も指定しなくても最低限の構成でビルドすることは出来ますが、最低限の構成だと IGES の読み書きすらまともにできないので、全ビルドするように指定しています。

./configure を実行する際、ログの最後のほうに出てくるレポートを参考にしてください。

[bash]

3rdparty mandatory products

freetype : yes
tcltk : yes

3rdparty optional products

gl2ps : yes
freeimage : yes
tbb includes : yes
tbb libraries : yes
qt : no (–with-qt=DIR option was not defined)

Component Build


FoundationClasses yes
ModelingData yes
ModelingAlgorithms yes
Visualization yes
ApplicationFramework yes
DataExchange yes
Draw yes
[/bash]

私の場合、サンプルとして付属する Qt プロジェクトでのみ利用されている「qt」の部分以外は、全て有効にしました。freetype、tcktk が欠落していると、Component の Visualization 以下のビルドができません。tbb は Intel の CPU で効率的にマルチスレッドを実現するためのライブラリです。

参考までに、私の Debian では次のパッケージをインストールしました。

[bash]
sudo aptitude install libxmu-dev libfreetype6-dev
sudo aptitude install tcl-dev tk-dev
sudo aptitude install libgl2ps-dev libfreeimage-dev libtbb-dev
[/bash]

それぞれの開発パッケージに含まれるヘッダファイル、ライブラリファイルは、sudo dpkg -L パッケージ名 で確認することができます。これを基に、configure オプションの –with-tbb-include= といったオプションを指定します。
どの Component が必要か取捨選択できるようになるまでは、とりあえず全部動くようにしておくのが無難でしょう。上の configure のレポートのようになったら、後は make するだけです。

[bash]
make
[/bash]

ちなみに、最後の make は非常に時間がかかります。私の環境(core i5/x86、SSD、シングルスレッドビルド)で、396分かかりました。

ビルド後のチェック

ビルドが完了したら、そのまま sudo make install してもいいのですが、コワイのでまずちゃんと使えるかチェックします。

正常にビルドができていれば、共有ライブラリは ros/adm/lin/amk//.libs/lib.so に生成されているはずです。また、ヘッダファイルは ros/inc に格納されているので、次のようにオプションを指定すれば、OpenCASCADE を利用したアプリケーションのビルドをすることが出来ます。

[bash]
cd ros
mkdir mysrc
cd mysrc
vim test.cpp
cat test.cpp

include <stdio.h>

include "Standard_Boolean.hxx"

// サンプルプログラム
int main()
{
Standard_Boolean n = Standard_True;
printf("%d\n", n);
return 0;
}
g++ -lTKernel -I/tmp/occ660/ros/inc -L/tmp/occ660/ros/adm/lin/amk//.libs test.cpp
LD_LIBRARY_PATH=/tmp/occ660/adm/lin/amk/
/.libs ./a.out
1
[/bash]

OpenCASCADE で利用される基本型 Standard_Boolean を用いたサンプルプログラムをビルドし、実行した模様です。最終行のように「1」が表示されれば、OpenCASCADE が利用できるようになっています。

インストール

正常に使えている事が確認できたら、次のコマンドでインストールします。

[bash]
make install
[/bash]

これでライブラリは /usr/local/lib、ヘッダファイルは /usr/local/inc に格納されます。また、DRAWEXE といった実行ファイルが /usr/local/bin に設置されます。

最後に、OpenCASCADE のサンプルプロジェクトなどで ros ディレクトリ以下のファイルを利用しますので、作業ディレクトリを

[bash]
mv /tmp/occ660 /opt/
[/bash]

しておき、$HOME/.profile に $CASROOT=/opt/occ660/ors を追記しておきます。

Windows でのビルド

Windows における OpenCASCADE は、標準ビルド環境が Microsoft Visual Studio のソリューションファイルとして提供されている為、前項のような開発者の環境に合わせた条件付きコンパイルがやりづらくなっています。基本的な手順は、6.5.4 の手順と変わりません。

OpenCASCADE のオブジェクトを WebGL で表示する

OpenCASCADE の Python 実装である PythonOCC の人が、2011年の1月のブログ記事で OpenCASCADE のオブジェクトを WebGL 向けに出力する方法を紹介しています。2年以上経った今、ウェブブラウザも WebGL に対応してきましたし、OpenGL 3.x をサポートしたオンボードチップも一般化してきました。精密な曲線・曲面表現の問題を抜きにすると、WebGL という手軽に三次元モデルが表示できる環境は魅力的だと考えています。メタセコイアの MQO 形式も WebGL で表示ができるよう作っている方もいますし、夢は広がるばかりです。

WebGL については、OpenGL ハードウェアにレンダリング作業を投げたり、HTML の img タグの data スキーム内のデータをリアルタイムに書き換えてアニメーションさせている、という程度の漠然とした知識しかありませんでした。綺麗なデモを見てみると、ちゃんと JavaScript を勉強してやってみたいなあ、花形的なジャンルだなあとは思いつつ…、言語自体は綺麗で好みなのですが、なんだかなあ。

先日、弟と呑みに行って WebGL の話が出て、PythonOCC の人が OpenCASCADE のオブジェクトを WebGL 向けに出力していることを思い出しました。

こちらのデモページのように、OpenCASCADE で作成したオブジェクトがウェブブラウザの中で、ぐりぐり動かせて三次元表示されています。ソースコードを見てみると、TopoDS_Shape オブジェクトを三角形メッシュ化して、WebGL で読み込み、表示ができるフォーマットに出力し直しているようです。

私自身は、PythonOCC を利用していないので、このコードを参考に C++ で実装し直そうと思いました。

#include <iostream>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <BRepTools.hxx> // BRepTools::Read()
#include <BRep_Tool.hxx> // BRep_Tool::Triangulation()
#include <BRep_Builder.hxx>
#include <BRepMesh.hxx>
#include <TopExp_Explorer.hxx>
#include <Poly_Triangulation.hxx>

#define MESHPREC 1000.0

using namespace std;

int main(int argc, char** argv)
{
  // 引数チェック
  if (argc != 2) {
    cerr << "The brep2webgl convert an OpenCASCADE Brep file format\n"
      "to ECMAScript script for WebGL.\n"
      "Usage:\n"
      " brep2webgl infile.brep > outfile.js\n"
      << endl;
    return 1;
  }

  // 読み込み
  TopoDS_Shape shape;
  BRep_Builder bb;
  BRepTools::Read(shape, argv[1], bb);
  if (shape.IsNull()) {
    cerr << "Convertion incompleted." << endl;
    return 1;
  }

  cout <<
  "var Shape = function() {"
    "var scope = this;"
    "THREE.Geometry.call(this);";

    cout << endl;

    BRepMesh::Mesh(shape, MESHPREC);
    TopExp_Explorer ex(shape, TopAbs_FACE);
    double scale = 0.001;

    for (int vindex = 0; ex.More(); ex.Next()) {
      TopoDS_Face face = TopoDS::Face(ex.Current());
      TopLoc_Location loc;
      Handle(Poly_Triangulation) poly = BRep_Tool::Triangulation(face, loc); // 三角形パッチ化
      const Poly_Array1OfTriangle& tris = poly->Triangles();
      for (Standard_Integer i = tris.Lower(); i <= tris.Upper(); i++) {
        const Poly_Triangle& tri = tris.Value(i);
        Standard_Integer n1, n2, n3; // ノードインデックス
        if (face.Orientation() == TopAbs_REVERSED)
          tri.Get(n3, n2, n1);
        else
          tri.Get(n1, n2, n3);
        gp_Pnt p1 = poly->Nodes().Value(n1);
        gp_Pnt p2 = poly->Nodes().Value(n2);
        gp_Pnt p3 = poly->Nodes().Value(n3);

        cout <<
          "v(" << p1.X() * scale << "," << p1.Y() * scale << "," << p1.Z() * scale << ");"
          "v(" << p2.X() * scale << "," << p2.Y() * scale << "," << p2.Z() * scale << ");"
          "v(" << p3.X() * scale << "," << p3.Y() * scale << "," << p3.Z() * scale << ");"
          "f3(" << vindex + 0 << "," << vindex + 1 << "," << vindex + 2 << ","
          << p1.X() << "," << p1.Y() << "," << p1.Z() << ","
          << p2.X() << "," << p2.Y() << "," << p2.Z() << ","
          << p3.X() << "," << p3.Y() << "," << p3.Z() << ");";
        cout << endl;
        vindex += 3;
      }

    }

    cout
      << endl << "this.sortFacesByMaterial();"
      << endl << "function v(x,y,z){scope.vertices.push(new THREE.Vertex(new THREE.Vector3(x,y,z)));}"
      << endl << "function f3(a,b,c,x,y,z,x2,y2,z2,x3,y3,z3){"
      << endl << "scope.faces.push(new THREE.Face3(a,b,c,"
      << endl << "[new THREE.Vector3(x,y,z),new THREE.Vector3(x2,y2,z2),new THREE.Vector3(x3,y3,z3)]));}"
      << endl << "}"
      << endl << "Shape.prototype=new THREE.Geometry();"
      << endl << "Shape.prototype.constructor=Shape;";

  return 0;

}

ビルドは次のとおり。BRepToolとMeshを使っているので、それぞれライブラリを指定してやります。

$ g++ -g -lTKernel -lTKMath -lTKBRep -lTKMesh -L/usr/lib/opencas -I/usr/include/opencascade -o brep2webgl brep2webgl.cx

このプログラムは、引数に与えられた BRep ファイルを読み込んで、内容物を三角形メッシュ化して前述の環境 pythonocc_webgl.html および Three.js で読み込める JavaScript 形式のデータを標準出力に吐きます。 実際に実行するには、次のようにタイプします。

$ brep2webgl test.brep > shape.js

標準出力にスクリプトが印字されますので、shape.js にリダイレクトをして保存し、pythonocc_webgl.html で読み込めば三次元モデルデータが表示されます。

試しに制作したデモをこちらに置いておきます。結構大きめのIGESファイルをBRepに変換後、上記のプログラムを介してWebGLで表示できるようにしたものですので、読み込みに時間がかかるかもしれません。 ※平面の法線ベクトルの関係で、デモを開いた状態では、ほとんど何も表示されていないかもしれません。マウスの左ボタンで画面を軽くドラッグしてやると、物体が回転してものが表示されるようになるはずです。