3/26メモ

heroku

わかりやすい。

heroku 上で Rails アプリが動くところまでざっと見てみました。てか、heroku の前にまともに Rails の勉強しなきゃ。

ほほう。

vagrant

上の heroku の動画で vagrant を使っていて、docker や virtualbox はコマンドラインからガリガリ使っていたけど、vagrant は名前くらいしか知らなかったのでちょっとメモしておく。

うんうん、virtualbox のイメージも空から降ってくるべきですよね。

swig

sirenmruby から MRI に移植してるんだけど、どうせなら SWIG で OpenCASCADE ラッパーな ruby-occt を作ってゴニョゴニョすべきかなと思いつつ

を見てたんだけど、PythonOCC では大量の SWIG 用ラッパーファイルで頑張ってらっしゃる。*.i だけなら流用できるのかなー、でも python なんちゃらって文字があるぞ。めんどいなあ…。そもそも siren は OCCT の無駄に多いクラスや機能を適度にまとめて使えることが利点だから、フル機能の OCCT を使いたいんだったら PythonOCC やネイティブな OpenCASCADE を使えばいいと思ってたりして悩みどころです。

Kinect 360 買っちゃった

前々回と前回の OpenCV まわりに関連して、とうとう Kinect を購入しました。Kinect for Windows じゃなく 360 でもとりあえずは研究に使えるらしいし、ドライバや SDK が厳格そうな Windows で開発するわけでもない、360なら価格も中古で低価格程度ということで Amazon で5000円の中古をポチ。ヤフオクで2000円弱で出品されてるんですが、動作保証と電源ケーブル同梱版、手続きの早さで割高だけど Amazon のを購入しました。

月曜日(明日)届く予定です。楽しみ。Kinect の到着を待つ間、Python で書いていた(というかコピペしてた)ステレオマッチング処理を Ruby で書き直そうと思ってドキュメントを見てたら ruby-opencv には StereoSGBM()ない模様

こんな時に颯爽と実装してプルリクをぶん投げられる人になりたい…。

デジタルデバイス処分

PS3、楽天 KOBO、NEC LifeTouch NOTE、Lavie Tab W 708 など使ってないデバイスを処分中。ハンディカムや液タブも売り出そうか悩み中です。

OpenCASCADE 6.9.0 を LMDE でビルド

先日、自宅サーバの Debian GNU/Linux Jessie 32bit 版で OpenCASCADE 6.9.0sirenビルドしました

自宅サーバは、SSH で外部からアクセスして開発ができるので便利である一方、オンボード Atom 搭載の省電力サーバなのでコンパイルにはもの凄く時間がかかります。そこで、Intel Core i7 を搭載した普段使い用のラップトップでもビルドしようと思います。

ラップトップは、LMDE(Linux Mint Debian Edition) の無印、あえて言うなら version 1 です。先日、LMDE 2 がリリースされましたが環境を壊すかもしれないのが怖くてまだ移行していません。なお、こちらは 64 bit です。

OCCT 6.8.0 ベースの siren をビルドして開発に使っていたので、こちらも難なくビルドできると思いきや、VTK を指定しないと Visualization 以下のモジュールが上手くビルドすることができませんでした。

configure の出力では、VTK は optional 扱いになっていたのですが、必須なのかなあ…?たしかに先日の Debian の記事でも明示的に VTK を指定していました。なお、VTK は自分の記憶が正しければ 6.8.0 の時点で既に OCCT に利用されていたようですが、その時は確実に optional だったはずです。(VTK をインストールしなくても Visualization 以下のビルドが通っていました)

ないなら入れてしまえと apt-cache search vtk するも、バージョン 5.8 しか公式リポジトリにはありませんでした。試しに libvtk5.8, libvtk5-dev を install して OCCT 6.9.0 をビルドしてみても、やはりビルドが通りません。 なお、Debian の Jessie には公式リポジトリに VTK 6.1 があったため、APT 経由で入れたらビルドが通りました。

ということで、ソースコードを取得して自分でビルドしましょう。VTK の公式サイトのダウンロードページから、対象となる 6.1 の tar ボールをダウンロードします。

cd /tmp
wget http://www.vtk.org/files/release/6.1/VTK-6.1.0.tar.gz
tar zxvf VTK-6.1.0.tar.gz
cd VTK-6.1.0

README.html にビルド方法が載っているので、それに従ってビルドします。

cmake.
make

README.html では cmake ではなく curses 版の cmake である ccmake と書いてありますが、cmake でも通ります。

ビルドにはもちろん CMake が必要です。また、依存しているライブラリもいくつかあると思いますが、幸運なことに自分の環境では追加ライブラリを指定することなく全てのビルドが成功しました。VTK を Debian で APT 経由からインストールした際、200 MB 前後の依存ライブラリがどっと降ってきたので心配でした。ヨカッタ。

例に漏れず、インストールは次のようにします。

sudo make install

インストールによって、ヘッダファイルは /usr/local/include/vtk-6.1 以下に、ライブラリファイルは /usr/local/lib 以下に設置されました。インストールパスは cmake オプションで指定できるはずです。

さて、これで OCCT 6.9.0 のビルド環境は整ったはずです。前回の手順どおりに、一気にやっちゃいます。

cd /tmp
wget http://files.opencascade.com/OCCT/OCC_6.9.0_release/opencascade-6.9.0.tgz
tar zxvf opencascade-6.9.0.tgz
cd opencascade-6.9.0/
./build_configure
./configure -prefix=/opt/occ690 \
-with-vtk-include=/usr/local/include/vtk-6.1 \
-with-vtk-library=/usr/local/lib

configure の出力で Visualization 以下のパッケージもすべて「Yes」になっていることを確認し、

make -j8 install

で、ビルド&インストール。 ビルド時間を計測すると次のとおりになりました。

real  17m11.411s
user  97m46.908s
sys   5m26.720s

前回、3時間以上かかっていたビルドは17分で終わりました。これは強い。

6.9.0 を用いて siren をビルドし直すとこちらでもちゃんと動いているようです。ひとまず安心です。

OpenCASCADE 6.9.0 を Debian でビルド

先月の12日にリリースされた Open CASCADE の 6.9.0 をビルドしました。公式のロードマップでは 6.x 系列は 6.8 が最後で、次は 7.0 になるとの話だったはずですが、何だかんだで 6.9 がリリースされた模様

v6.9 のリリースノートを読んではいましたが、ここのところ忙しくて対応ができなかったので出遅れてしまいました。

ここ数バージョンは、ビルドの仕方マニュアルがいろいろと整備されてきて、時代に合わせてなのか、ビルド環境ごとの Markdown なドキュメントも付属しています。このブログで改めて紹介する必要性もあまりなくなってきました。詳しい内容は公式ドキュメントに任せるとして、要点だけかい摘まんで紹介したいと思います。

公式ドキュメンテーション

v6.9.0 の内容は次のドキュメントで網羅されています。

v6.9.0 のリファレンスマニュアルは、ソースコード tar ボールや Windows インストーラー内に同梱されています。最も最新バージョン(開発版)のリファレンスマニュアルであればこちらで参照できます。

v6.9.0 リリースの要点

モデリング・アルゴリズム

ファジー ブール演算

これまでのバージョンでは、ソリッドモデル等で行うブール演算時に対象と対象がちょうど同一の位置にある面や線を共有して存在していた場合、意図した演算結果にならない事が多くありました。意図した結果にならない場合はまだ良かったんですが、いつまで経っても計算が帰ってこない、または例外を吐いてお亡くなりになるケースもありました。この点が改善されているようです。

複数の引数を取るブール演算

これまでのブール演算では TopoDS_Shape を二つ用いて、Fuse, Common, Cut 等の演算を実行していました。あんまり詳しく見てませんが、同時に複数の TopoDS_Shape を演算させることができるようになるようです。これまで数回の処理で書いていたものが一度にできるわけですが、複数のソリッド群の一つに reverse させたソリッドを混ぜて Common に投げて、結果的にその部分だけは Cut させる動作にするなど、より「ブーリアン」っぽい演算としてコーディングできるようになりそうです。

ビジュアライゼーション

選択処理を再設計して、より良いパフォーマンスに

OCCT のビジュアライゼーションは長いことほったらかしになっていた様子で、ちょっと貧弱でした。これは、あくまでも OCCT は三次元幾何演算とトポロジックな形状の演算がメインであり、代替ライブラリーを用いて解決できるビジュアライゼーションは二の次でも大丈夫!という背景もあります。 実際、OCCT を採用している有名なオープンソース三次元 CAD ソフトの FreeCAD は、OCCT 提供のビジュアライゼーションを使用せずに別のライブラリで三次元ビューを提供しています。 ただ、ここ数バージョンでは、前述の幾何を扱うことろがだいぶ安定してきたのか、二の次だったビジュライゼーションが次々と強化されており、パフォーマンスも改善しています。まだ実験的な印象ではあるものの、レイトレーシングレンダリングの採用、GLSLシェーダが読み込めるようになっていることなど、単なる計算ライブラリから本当に「CAD」として必要な機能の強化が見てとれます。 後述する VTK の採用も注目すべき点です。

OpenGL ES 2.0 互換

ビジュアライゼーションの改良の特記すべきもう一つの点は、iOS, Android などのモバイル端末への対応です。OpenGL ES はモバイル端末向けの OpenGL のサブセットです。OCCT の幾何ライブラリの部分は、早い段階から Android にも移植されて動作していましたが、ビジュアライゼーションはそのままではビルドできませんでした。ES 互換となることにより、これらの環境へもシームレスな移植が可能となりそうです。

シェイプを表示する際の三角形メッシュ化の制御

コンテキストにシェイプを追加する際、これまでは必要に応じて自動的にシェイプの描画データである三角形メッシュを内部で生成していました。これを外側から無効にするオプションが導入されたようです。 形状の変形はなく、大量のシェイプを回転させるだけでも再三角形メッシュ化が走って表示速度が落ちていましたが、それらを動的に制御することができそうです。(※回転も行列演算している意味では変形(トランスフォーメーション)ですが、自由曲面の式まで遡って、形状の三角形メッシュを再構築する必要すらない場合も往々にあります)

その他

Intel TBB ライブラリを使わない並列処理のサポート

こちらも詳しく追っていませんが、自前で並列処理を行う環境が整いつつあるようです。

サンプルに AndroidQt, JniViewer for Android が追加

Android 環境のサンプルも充実してきました。

JniViewer

JniViewer for Android

AndroidQt

GUI は QML を使って書いてあるみたいです。本当に WPF/.NET Framework なんて使ってる場合じゃないです。

AndroidQt

その他のサンプルに、Qtデスクトップ版、MFC、C# があります。C# では WPF ベースのものと System.Windows.Forms 版があり、今のご時世でいうと半ばどうでもいいですが、Direct3D デモも追加されています。

v6.9.0 の全体的な感想

幾何演算部分の改良は喜ばしい限りで、ビジュアライゼーションの強化も頑張ってほしいところです。ようやくモバイル端末のサポートも視野に入れてきているようですが、個人的にはいっそのこと三次元ビューアは WebGL 環境へシフトしていくのもアリなんじゃないかなぁと考えています。OCCT をサーバ上で並列処理させて、クライアントの WWW ブラウザ上で表現する…素晴しいじゃないですか。最近は NW.js (Node.js + Webkit)のようなツールキットも存在しますので、ライトウェイトなビューアーからワークステーションで動かす CAD まで環境に依存しないビジュアライゼーション機能になっていけばいいな、と思っています。え?お前が Context や Viewer, View を JavaScript で再実装しろって?それが仕事になればいいんだけどなぁ…;)

v.6.9.0 のビルド

ビルド方法は次のドキュメントに詳しく書いてあります。

なお、このドキュメントはソースコード tar ボールを解凍して生成されるdoxディレクトリ以下に markdown ファイルとして収録されています。(逆に言うと、上記の HTML はそれらのファイルを doxygen にかけたものと思います。)

ビルド環境

OCCT のビルド環境もかなり充実してきており、以前のようにチューニングしながらあれやこれやする必要もほとんどなくなりました。公式サポートしているのは次の環境です。

  • Automake
  • CMake
  • CMake + ADT (Android SDK)
  • Code::Blocks (Mac OSX)
  • Xcode (Mac OSX)
  • Microsoft Visual C++ (Windows)

また、パフォーマンスチューニングレポートでは Linux 版の Clang/LLVM でも検証されているようです。

今回は一番馴染みがある Automake でいきたいと思います。

ビルド手順

作業ディレクトリに入り、tar ボールを落としてきます。ソースコードは前バージョンまではユーザー登録&ログインしないとアクセスできなかった気がしますが、今回から下記の URL で落とせるようです。なお、git での clone は登録が必須です。

cd /tmp
wget http://files.opencascade.com/OCCT/OCC_6.9.0_release/opencascade-6.9.0.tgz

解凍してディレクトリにはいります。

tar zxvf opencascade-6.9.0.tgz
cd opencascade-6.9.0/

configue をビルドして configure します。オプションについては後述します。

./build_configure
./configure -prefix=/opt/occ690 \
-with-vtk-include=/usr/include/vtk-6.1 \
-with-vtk-library=/usr/lib/i386-linux-gnu/

make して install します。

sudo make -j2 install

とりあえずこれだけです。便利。

貧弱な自宅サーバ(Debian GNU/Linux、32bit環境、Intel Atom)では、make で

real    183m57.121s
user    336m24.168s
sys     16m36.896s

となりました。

configue オプション

-prefix=

最終的にビルドしたファイルをインストールする場所です。自分は /opt/occ690 に指定しています。

-with--include=, -with--library=

ヘッダのインクルードパスとライブラリパスを追記しています。VTK が見えてなかったようなので、明示的に指定しました。

その他のオプションはこちらを参考にしてください。

ビルド時のテクニック

configure では、ビルドする環境にインストールされているライブラリの有無によって OCCT のモジュールとよばれるライブラリ群の Makefile を生成します。

モジュール > ツールキット > パッケージ > クラスや列挙型など

OCCT は上記のように階層構造を持ったライブラリです。ツールキットの部分が共有ライブラリファイル(.so)に当たります。Windows ではダイナミックリンクライブラリ(.dll)になります。モジュールはそれらをまとめて用途別にグループにしたものだと思ってください。

モジュールには次のものがあります。

  • FoundationClasses … 共通関数などを提供
  • ModelingData … 幾何形状をデータとして表現する機能
  • ModelingAlgorithms … 幾何形状を構築する機能
  • Visualization … 幾何形状を画面に描画する機能
  • ApplicationFramework … OCCT を使ったアプリのための有用な機能
  • DataExchange … 幾何データファイルのサポート(IGES,STEP,STLなど)
  • Draw … テスト環境

ざっくりと説明すると上に行くほど依存関係が少なくてすみます。詳細な依存関係はリファレンスマニュアルの図のとおりです。

occt modules

さらに、個々のモジュール内のツールキットの中で依存関係があります。なお、IGES などのデータファイルをサポートするには、GUI が必要がない場合でもビジュアライゼーションやアプリケーションフレームワークの各ツールキットを全て背負い込まなければならないかというと、実はそうではありません。具体的にはモジュール同士の依存関係の奥にはツールキット同士の依存関係があり、それを満たしていればこの図の限りではありません。今のところ、モジュールを越えた関係図はないので、リファレンスマニュアルを見て何が必要なのかを調べる必要があります。

さて、ビジュアライゼーション以下のモジュールは、さらに多くの別の人(サードパーティ)が開発しているグラフィック系のライブラリに依存しています。

そのため、グラフィック系のライブラリがない環境で configue すると、ビジュアライゼーション以下のモジュールが無効になった Makefile が生成されます。

configure の出力に各モジュール名と Yes/No といった表示が出力されるので、それでモジュールが有効になっているか無効になっているか、無効の場合はどのライブラリが足りていないのかを表示してくれます。以下に私の環境の configure 出力を示します。

3rdparty mandatory products       
---------------------------------
freetype      : yes 
tcltk         : yes 

3rdparty optional products       
---------------------------------
gl2ps         : no (--with-gl2ps=DIR option was not defined)
freeimage     : no (--with-freeimage=DIR option was not defined)
tbb includes  : no (--with-tbb-include=DIR option was not defined)
tbb libraries : no (--with-tbb-library=DIR option was not defined)
opencl        : no 
qt            : no (--with-qt=DIR option was not defined)
vtk           : yes 

Component                   Build
--------------------------  -----
FoundationClasses           yes 
ModelingData                yes 
ModelingAlgorithms          yes 
Visualization               yes 
ApplicationFramework        yes 
DataExchange                yes 
Draw                        yes 

「3rdparty mandatory products」は必須ライブラリ、「optional」は指定があったら使うライブラリです。必要なライブラリとそのバージョンは、以下にまとめてあります。

Open CASCADE Technology: Overview

Debian GNU/Linux jessie 32bit 版では、すべてのサードパーティ・ライブラリを標準リポジトリからインストールすることができました。(ソースコードの取得とビルドは不要)

また、テスト環境である Draw や Qt のサンプルプロジェクトをビルドする必要がなければ、 Tcl/Tk や Qt のインストールは必要ありません。 上記の「mondatory products」に Tcl/Tk が入っているのは、すべてのモジュールをビルドする際に必須、という意味だと思います。

siren をビルド

OCCT を使った siren というソフトを作ってます。OCCT が提供する三次元幾何演算機能を Ruby で簡単に記述することができ、ちょっとした演算なら手軽にすることができます。

上記でビルドした OCCT 6.9.0 を使って siren の最新リビジョン(OCCT6.8.0ベースで記述)をビルドし直してみると、あっけなくビルドが通りました。クラス名が変更されたり、siren で使っている機能が変更されることがなかったようです。

ひとまず安心しました。


Open CASCADE 6.7.1 をビルドする

開発環境の準備

g++(GNUコンパイラコレクションのC++コンパイラ一式)は既にインストール済みであることを前提としています。

aptitude install automake libtool

nginx を動かして小さなウェブサイトをホスティングしているくらいのうちの Debian wheezy では、上記の二つだけ入れれば良いみたいでした。

Open CASCADE の準備

まず、作業ディレクトリを/tmp以下に作って移動します。

cd /tmp
mkdir occ
cd occ

作業ディレクトリは権限があればどこでも構いません。以後、このページでは/tmp/occを作業ディレクトリのルートにします。適宜読み替えてください。

OCCT の開発版は、開発サイトに登録して、印刷した契約書にサインして、スキャンした PDF を返送しなければ手に入りません。開発版は git で clone すれば良いですが、大抵の場合は正式版で十分ですので、公式サイトから入手することになります。OCCT の公式サイトでは Windows 環境ではビルド済みのバイナリとソースコード、Linux 環境でも大手ディストリならばディストリごとの apt や yum リポジトリにパッケージが準備されていますが、後者は最新版パッケージがなかったり、最近では Debian の公式リポジトリから無くなってしまったりとアレですので、ソースコードからビルドします。ちょっと手間がかかりますが、一度慣れてしまうと、必要な機能だけ切り分けてビルドすることができるようになりますので、エンドユーザ向けアプリケーションや WWW サーバ上で動かす場合など、用途に応じた最適化ができます。また、その気になれば Android といった携帯端末向けにビルドすることもできます。

wget -O occ671.tar.gz http://...
tar xvf occ671.tar.gz

解凍すると/tmp/occ/opencascade-6.7.1というディレクトリが生成されます。

ビルド方法については以下のディレクトリにドキュメントがあります。

/tmp/occ/opencascade-6.7.1/dox/dev_guides/building

ここ数バージョンでOCCTのリファレンスは doxygen に統一されました。ここにあるドキュメンテーションは markdown です。

最小構成ビルド

最低限の機能のみしか使わない場合は、次のコマンドだけでいけます。ビルド自体は automake 以外にも CMake、MSVC、XCode 環境でもできます。 また、100%かどうかは確認していませんが Clang/LLVM としてもビルドできるようです。

./build_configure 
./configure
make

OCCT には1万を超えるクラス群が存在しており、フルビルドは数十分、場合によっては数時間は平気でかかります。 就寝前や外出前に make していくことをオススメします。

なお、これから OCCT を触ってみたい人には、この最小構成ビルドはオススメしません

mruby: C++のクラスをラッピングしてRubyクラスとして使えるようにしてる途中

sirenの改修を行なっています。これはもともと、Open CASCADE の C# アプリケーションサンプルに含まれていた OCCTViewer クラスを参考にして組んだものがベースで、C# アプリケーションのコードとネイティブな DLL の間を取り継いで、機能を提供する sirenenv が動いています。

sirenenv の内部をさらに細かく分けると、C++/CLI の構文で書かれたインターフェイス部分と C++ 構文のみで書かれたコア部分に分けることができます。C++ 部分も含め、結局 /clr オプションでコンパイルしているので C++/CLI として解釈されるんですが、将来の「脱 .NET」を目指して切り分けを進めています。(厳密に構文が分かれてない部分もまだあると思われます)

C++部分をさらに分けると、Open CASCADE のオブジェクトやビューを扱う描画&幾何演算部と mruby インタプリタを扱うスクリプト演算部の2種類に分かれています。

siren のロゴを作ってみました

私個人がオープンソースで開発をしている三次元ベースの簡易 CAD システム「siren」のロゴを作成してみました。

今、自分が勤めている会社は海に関する仕事がメインで、マシンのホスト名やシステムの開発コード名に、海に関する用語をつけるのが慣例となっています。この簡易三次元 CAD は、会社の業務としてではなく、あくまでも個人的に開発しているものなんですが、その慣例に倣うかたちで「siren(セイレーン)」と名付けました。セイレーンは、ギリシャ神話に登場する海の怪物のひとつで、岩礁から美しい歌声で歌い、その声で船員たちを惑わし、航海中の船を遭難させたり難破させたりする怪物です。上半身は女性、下半身は鳥という奇妙な形をしているそうで、時代が下るにつれ、人魚の姿をしているとされたようです。そして、大きな音を出すサイレンはこのセイレーンが語源らしいです。

siren_logo_simple

ロゴについて

セイレーンとサイレンを組み合わせて描いてみました。途中から気付いたんですが、鮮かな色の影絵調+黒い文字にアンダーラインってものすごくFFのロゴっぽいですね。もうちょっと動的な水飛沫みたいなのを追加しようかとも思ったのですが、さらに天野喜孝風になるとアレなのでやめておきました。ちなみに、セイレーンのロゴについて調べているとスターバックスのマークもセイレーンがモデルらしいです。

当初はセイレーンだけだったんですが、siren の開発を手伝ってくれている弟に見せたところ「サイレンにしようぜ」といっていたので、試しにサイレンを立ててみたら、「セイレーンがサイレンをつかっている」という図らずしも良い具合なミスマッチ感が生まれました。

フォントなどのライセンスまわりがクリアになったら、そのうちクリエイティブコモンにしようと思っています。まだソースデータは公開していません。

読みについて

公式では siren を「セイレーン」と呼び、カタカナ表記もそうするように統一しています。ただし、読み(発音)については自由してもらって構いません、というのが公式のスタンスです。開発サイドではもっぱら「サイレン」と呼んでいます。日常的にセイレーンと呼ぶのは、自分で名付けておきながら、なんだか恥かしい感じがするのと、呼びやすさの意味からです。また、「シレン」と呼ぶ人もいました。通じさえすればいいので、どれも間違いではありません。 今のところユーザーはかなり少ないと思うし、あまり気にする人はいないかなー。

OpenCASCADE で曲面を作成する

今回はいよいよ曲面を作成していきたいと思います。

平面までなら、なんとか独自に三次元幾何ライブラリーをこしらえて扱うことができるかもしれませんが、自由曲面を扱うシステムを組もうとすると、とても独自開発ではやっていけません。そんな時、OCCが力を発揮するわけです。

また、OCCではトポロジカルな扱いをする上で、平面と曲面の明確な差はあまりありません。 もちろん、作成時に必要となる要素は平面の方がより限定的ですが、トポロジーを考えた場合、平面も曲面の一部という考え方のほうがより自然です。

OCC上でトポロジカルな面(平面も曲面も)を扱うには、前回の平面の作成の際に出てきた面クラス TopoDS_Face を用います。

通過点を指定してベジエ曲面を作成する

uv方向にマス目状に並べた通過点を指定することで、曲面を作成します。一番、直感的で簡単な曲面の生成方法ですが、これは座標値の羅列から uv 曲線群を定義して面を生成しているので、内部的にはパラメトリックな曲面の式で構成されています。

コード

TColgp_Array2OfPnt poles(0, 3, 0, 3);
poles.SetValue(0, 0, gp_Pnt( 0,  0, 20));
poles.SetValue(0, 1, gp_Pnt( 0, 10, 20));
poles.SetValue(0, 2, gp_Pnt( 0, 20, 20));
poles.SetValue(0, 3, gp_Pnt( 0, 30, 20));
poles.SetValue(1, 0, gp_Pnt(10,  0, 10));
poles.SetValue(1, 1, gp_Pnt(10, 10, 10));
poles.SetValue(1, 2, gp_Pnt(10, 20,  5));
poles.SetValue(1, 3, gp_Pnt(10, 30,  0));
poles.SetValue(2, 0, gp_Pnt(20,  0,  0));
poles.SetValue(2, 1, gp_Pnt(20, 10,  5));
poles.SetValue(2, 2, gp_Pnt(20, 20, 10));
poles.SetValue(2, 3, gp_Pnt(20, 30, 10));
poles.SetValue(3, 0, gp_Pnt(30,  0, 20));
poles.SetValue(3, 1, gp_Pnt(30, 10, 20));
poles.SetValue(3, 2, gp_Pnt(30, 20, 20));
poles.SetValue(3, 3, gp_Pnt(30, 30, 20));

Handle(Geom_BezierSurface) s = new Geom_BezierSurface(poles);
TopoDS_Face f = BRepBuilderAPI_MakeFace(s, 1.0e-7);

TColgp_Array2OfPnt クラスは、gp_Pnt を2次元配列として保持するためのコレクションクラスです。 このコードを実行すると、次のような曲面が生成されます。

また、Geom_BezierSurface クラスのコンストラクタには、各通過点のウェイトを設定することも出来、次のようにコーディングすることができます。

// poles はあるものとする ...

// ウェイトの設定
TColStd_Array2OfReal weights(0, 3, 0, 3);
weights.SetValue(0, 0, 1.0);
weights.SetValue(0, 1, 1.0);
// ...

Handle(Geom_BezierSurface) s = new Geom_BezierSurface(poles, weights);
TopoDS_Face f = BRepBuilderAPI_MakeFace(s, 1.0e-7);

poles の u, v の各店に対応したウェイトを TColStd_Array2OfReal クラスで設定していき、コンストラクタに渡してあげます。

NURBS 曲面を作成する

ワイヤーをスイープ(掃引)して曲面を作成する

複数のワイヤーをロフトして曲面を作成する

OpenCASCADE で平面を作成する

前回の曲線に引き続き、今度は平面を作成していきたいと思います。

前提

平面を決定するための要素は

  • 任意の3点を通る
  • 任意の1点と法線ベクトル

などのいずれかの条件が必要です。 さらに、有限平面を幾何的に決定するには、

  • 外形
  • uv方向の範囲

などの条件が必要となります。

一方無限平面は、画面に表示したり、重心位置や面積を計算したり、交線を発生させたり、さまざまな幾何的処理を行う際に不都合が多いのは確かです。また、コンピュータのリソースも有限です。

OCCでは、「無限平面」をジオメトリックに、式や値の集合として保持することは可能ですが、トポロジカルなシェイプ(形状)として保持することはありません。これは次のような見方ができるかもしれません。

  1. 直線 y = ax + b の式 ← ジオメトリーレベル、範囲は無限
  2. 1 の条件 かつ 0 <= x <= 10 ← トポロジーレベル、範囲は有限

トポロジーレベルでの条件(?)は、範囲を設定することだけではなく、行列を用いたトランスフォーメーションなどもそれに当たります。式以外でその形状を決定づける全ての要因がトポロジーであると言えます。

この部分の解釈は、私自身、幾何数学があまり得意ではないので上手く説明ができませんし、何か勘違いをしている部分もあるかもしれませんが、ジオペトリーとトポロジーの概念を詳しく説明したサイトはインターネット上にたくさんありますし、OCCでのコーディングを深めていく上で、雰囲気を感じとっていただければ…ということでお茶を濁します。すいません。

以後、このブログで直線、曲線、平面、曲面といった単語が出てきたら、すべて「有限~」であると思ってください。あんまり出てこないとは思いますが、有限か無限かが重要なポイントでは、明示的に述べたいと思います。

矩形平面を作成する

さて、実際にモノを作ります。ここで矩形平面と呼んでいる平面は、任意の三次元座標を基準にし、uv方向に大きさを持つ矩形上の平面のことです。下図のようなものです。

ここで平面を決定づける要素は

  • 基準点 pos[x, y, z]
  • 法線ベクトル norm[x, y, z]
  • v ベクトル vdir[x, y, z]
  • u 方向の範囲 umin, umax
  • v 方向の範囲 vmin, vmax

です。u ベクトルはありませんが、法線ベクトルと v 方向を示すベクトルの二つがあれば、この二つの外積から u 方向は決定されます。

コード

Standard_Real umin = -10, umax = 10;
Standard_Real vmin = -10, vmax = 10;

gp_Pnt pos(0, 0, 0);
gp_Dir norm(0, 0, 1);
gp_Dir vdir(0, 1, 0);
gp_Ax3 ax(pos, norm, vdir);
gp_Pln pln(ax);

TopoDS_Face f = BRepBuilderAPI_MakeFace(pln, umin, umax, vmin, vmax);

上で示した要素を定義して、BRepBuilderAPI_MakeFace クラスで平面を作成しているだけですので、あまり説明をすることはないと思います。

今回、初めて登場した gp_Dir は、名前の通り方向(Direction)を示す単位ベクトルです。この型を用いて API を呼び出したりする際、その方向だけが用いられ、大きさは無視されます。なので、

gp_Dir dir(1, 1, 0); // XY方向にナナメ

という指定の仕方をしても、用いられる際には正規化されていますので大丈夫です。

gp_Ax3 は、軸クラスです。gp_Ax3 のほか、gp_Ax1 と gp_Ax2 も存在します。

  • gp_Ax1 … 位置(gp_Pnt)と方向(gp_Dir)を持つ
  • gp_Ax2 … 位置(gp_Pnt)と方向(gp_Dir)とv方向(gp_Dir)を持つ
  • gp_Ax3 … Ax2 の要素に加え、右手系・左手系の設定を持つ

軸をカメラに例えると、Ax1 でカメラの位置と視線方向が決定でき、Ax2 では視線方向を軸とした視野の回転を制限することができるわけです。(決定要因が視線方向だけだと、視野における上がどちらの方向か定まりませんもんね)

さらに gp_Pln は、Ax3 の情報を持った「無限」平面です。持っている情報は Ax3 のそれと変わりませんので、意味論としての入れ物として解釈していいと思います。

あとは、BRepBuilderAPI を使ってトポロジカルな面 TopoDS_Face を作成しています。

1点だけ補足を述べておきますと、上記の例の pos は、あくまでもベースとなる無限平面を定義するものですので、uv 方向の範囲内(有限平面内)に存在している必要はありません。つまり、例えば原点(0, 0, 0)に位置定義をしておき、u が 100~200 の範囲、v が 50~80 の範囲、という定義も問題なく成立するわけです。

任意の外形を持つ平面を作成する

さて、次は任意の外形を持つ平面を作成します。図で示すと、次のような形状を作成します。

前項では、uv 方向の範囲を指定して平面を作成しましたが、この項からは「合わせ技」が登場します。具体的に述べますと、外形を一旦、ワイヤーというシェイプとして生成しておき、そのワイヤーを用いて平面を作成するわけです。

コード

百聞は一見にしかず。実際にコードを追っていきましょう。

BRepBuilderAPI_MakePolygon mp;
mp.Add(gp_Pnt(0, 0, 0));
mp.Add(gp_Pnt(10, 0, 0));
mp.Add(gp_Pnt(10, 5, 0));
mp.Add(gp_Pnt(20, 5, 0));
mp.Add(gp_Pnt(20, 15, 0));
mp.Add(gp_Pnt(15, 12, 0));
mp.Add(gp_Pnt(5, 12, 0));
mp.Add(gp_Pnt(3, 10, 0));
mp.Add(gp_Pnt(0, 0, 0));
mp.Build();

if (mp.IsDone()) {
    TopoDS_Wire w = mp.Wire();
    BRepBuilderAPI_MakeFace mf(w, Standard_True);
    mf.Build();
    if (mf.IsDone()) {
        TopoDS_Face f = mf.Face();
        return mrb_fixnum_value(::set(f, self));
    }
}

以前のポリラインを作成する記事で紹介したように、BRepBuilderAPI_MakePolygon クラスを使って、ポリラインを作成し、TopoDS_Wire として受け取っています。このワイヤーを用い BRepBuilderAPI_MakeFace クラスで平面を作成しています。

この MakeFace クラスは、前項と同じものですが、コンストラクタがオーバーロードされており、いくつもの方法で面を作ることが可能となっています。これは MakeFace に限らず、MakeEdge などのクラスも同様です。

MakePolygon クラスは、Wire() だけでなく Edge() や Shape() でも作成した曲線を返すことができますが、ここでは明示的に Wire として受け取っています。Wire を端的に表現すると「1個以上の Edge からなる連続した曲線群」で、連続性が保証されていることが重要なポイントです。単に「複数の Edge」で良ければ、Edge の配列かなにかで十分ですが、複数の Edge をひとつの Wire として表現するということは、同時にそれらの Edge が連続してつながっている状態であるということを表します。

閉じた領域である有限平面の外形となるには、外形を構成している Edge がバラバラに存在しているのではなく、ひと筆書きで連続していることが条件となるため、MakeFace の引数には Wire が用いられるわけです。

さらに条件をつけ加えると、その Wire は始終点がくっついて輪状になっている必要があります。外形線なのに、どこかが途切れていたりしたら、その平面の範囲を決定できませんよね。

BRepBuilderAPI_MakeFace クラスのコンストラクタの第2引数が Standard_True になっていますが、これは平面であることを強制するためのフラグです。 前項では gp_Pln クラスを用いて、作りたい面が乗っているベースとなる無限平面を明示的に指定していました。今回の Wire から平面を生成する方法では、そのような明示的な指定はありません。そのため、平面であることを強制するフラグが存在するのです。(このフラグを False にすると、曲面として面を張ります)

また当たり前ですが、平面を作成する場合、ポリラインの各通過点は同一平面上になければなりません。

オマケ: OCCにおける数値型について

OCCでは移植性向上のため、整数値、浮動小数点数、真偽値などの値を次のように定義しています。

typedef int           Standard_Integer;
typedef double        Standard_Real;
typedef bool          Standard_Boolean;
typedef float         Standard_ShortReal;
typedef char          Standard_Character;
typedef short         Standard_ExtCharacter;
typedef unsigned char Standard_Byte;
typedef void*         Standard_Address;
typedef size_t        Standard_Size;
typedef std::time_t   Standard_Time;
typedef const char*   Standard_CString;
typedef const short*  Standard_ExtString;

詳しい内容は、Standard_TypeDef.hxx に記載されています。このブログでも、これらの定義に則って表記しています。 複数のプラットフォームへの移植を考えてなくても、OCCを使ったコードではできるだけ上記の型名を用いておいた方がいいでしょう。

OpenCASCADE で NURBS 曲線を作成する

前回の点、線分、ポリライン作成に続いて、今回は曲線を作成したいと思います。

通過点を指定して曲線を作る

曲線の場合、座標値の集合である線分やポリラインと違い曲線を定義する式を組み立て(ジオメトリーレベル)、そこからトポロジーレベルのシェイプを生成する必要があります。

コード

Standard_Integer nb_pts = 4; // 点数

// 点配列クラスに4点設定
Handle(TColgp_HArray1OfPnt) pary = new TColgp_HArray1OfPnt(0, nb_pts - 1);
pary->SetValue(0, gp_Pnt(0, 0, 0));
pary->SetValue(1, gp_Pnt(10, 0, 0));
pary->SetValue(2, gp_Pnt(10, 10, 0));
pary->SetValue(3, gp_Pnt(20, 20, 0));

Standard_Real tol = 1.0e-7; // トレランス(幾何誤差の許容値)
GeomAPI_Interpolate intp(pary, Standard_False, tol);

intp.Perform();
Handle(Geom_BSplineCurve) hgeom_bspc = intp.Curve();
TopoDS_Edge e = BRepBuilderAPI_MakeEdge(hgeom_bspc);

新しいクラスがいくつか出てきました。順を追って見てみましょう。

まず、Handle(TColgp_HArray1OfPnt) ですが、これは名前のとおり、gp_Pnt クラスの1次元配列コレクション TColgp_HArray1OfPnt の Handle 型です。 詳しい Handle の解説は別記事でしたいと思いますが、この型で定義されたオブジェクトは、new でインスタンスを作っても OCC 内に実装された独自のガベージコレクションが自動的に管理をしてくれるので、解放の必要がないオブジェクトになります。 使い方は普通のポインタ同様、アロー演算子でメソッドを呼び出します。

SetValue() でインデックスと点座標値を渡していきます。インデックスは1から開始されるので、注意してください。

次の GeomAPI_Interpolate は、通過点からBスプライン曲線を作成するためのクラスです。コンストラクタの第2引数を Standard_True にすると、曲線の始終点を曲線的に連結して輪状の曲線を作成します。

Perform() メソッドで計算を行い、Curve() メソッドで、ジオメトリーレベルの曲線シェイプ Geom_BSplineCurve の Handle 型を取得することができます。

さらに、BRepBuilderAPI_MakeEdge に渡すことにより、最終的に扱う TopoDS_Edge を受け取ることができます。

NURBS 曲線の要素から NURBS 曲線を作る

前提

このブログをご覧の方はご存じの方が大半だと思いますが、NURBS 曲線の定義は次の 3 つの要素から成ります。

  1. 次数
  2. 制御点列
  3. ノットベクトル列

制御点は x, y, z 座標値のほか、ウェイト w を持ちます。また、ノットベクトルの数は次の式により決定されます。

ノット数 = (制御点数) + (次数 + 1)

サンプル用の要素

  1. 次数: 2
  2. 制御点列: [100, 0, 0, 1], [70, -10, 10, 1], [30, 40, 10, 1.2], [0, 0, 0, 1]
  3. ノットベクトル列: [0, 0, 0, 1, 2, 2, 2]

これらの要素を用い、OCC 上のトポロジカルなシェイプを作成します。

コード

TColgp_Array1OfPnt poles(0, 3);
poles.SetValue(0, gp_Pnt(100,  0,  0));
poles.SetValue(1, gp_Pnt( 70,-10, 10));
poles.SetValue(2, gp_Pnt( 30, 40, 10));
poles.SetValue(3, gp_Pnt(  0,  0,  0));

TColStd_Array1OfReal weights(0, 3);
weights.SetValue(0, 1.0);
weights.SetValue(1, 1.0);
weights.SetValue(2, 1.2);
weights.SetValue(3, 1.0);

TColStd_Array1OfReal knots(0, 2);
knots.SetValue(0, 0.0);
knots.SetValue(1, 1.0);
knots.SetValue(2, 2.0);

TColStd_Array1OfInteger mults(0, 2);
mults.SetValue(0, 3);
mults.SetValue(1, 1);
mults.SetValue(2, 3);

Handle(Geom_BSplineCurve) hgeom_bscurve = new Geom_BSplineCurve(
    poles, weights, knots, mults, 2, Standard_False);

TopoDS_Edge e = BRepBuilderAPI_MakeEdge(hgeom_bscurve);

上から、poles(制御点)、weights(制御点に対応するウエイト)、knots(ノットベクトル列)、mults(ノットベクトルの多重度)を設定し、Geom_BSplineCurve クラスのコンストラクタに渡しています。

ノットベクトルの多重度とはノットベクトルの各要素が「いくつ」続いているかを示すもので、この例では、

[0, 0, 0, 1, 2, 2, 2]

というノットベクトル列に対して、0 が 3 個、1 が 1 個、2 が 3 個と読み変え、

knots = [0, 1, 2]
mults = [3, 1, 3]

という引数を導き出すことができます。これは、例えば 0 が 100 個並ぶような巨大なノットベクトル列も

knots = [0]
mults = [100]

のように、シンプルに「圧縮」して表記できる利点があるためです。

この NURBS 曲線を表示してみると、次のようになりました。

B-Spline curve

オマケ: ライブラリはどれ?

OCCには65個もの共有ライブラリが存在しており、自分が使いたいクラスがどのライブラリで定義されているのか、迷うことがあります。

OCCを使ったプログラミングで最低限必要なライブラリは、TKernel.so です。TKernel は間接的に tbb などのライブラリを参照しています。

さらに、自分が使いたいクラスのライブラリを調べるには、クラス・リファレンスの情報を参考にすればいいと思います。

例えば、BRepBuilderAPI_MakeEdge クラスを使いたい場合、クラス・リファレンスのページを参照すると、次のようになっていると思います。

ここで、赤枠で示された Toolkit の名前がそのままライブラリ名になっています。この例では、BRepBuilderAPI_MakeEdge は TKBRep と TKTopAlgo が必要ということになります。

この情報を基に

g++ -lTKernel -lTKBRep -lTKTopAlgo -L/usr/lib/opencas -I/usr/include/opencascade source.cpp

という具合にリンクしてあげると良いと思います。

ちなみに Microsoft Windows のダイナミックリンクライブラリの場合も、同様に調べることができます。

OpenCASCADE で点、線分、ポリラインを作成する。

OpenCASCADE関連の日本語情報がほとんどありませんので、Tipsというか、使い方というか、自分用のメモ的なものを小出しにしてまとめていこうと思います。

OCCで形状を作成する場合、特殊なものでなければ BRepBuilderAPI を用います。点、線分、ポリラインも例に漏れずこれを使います。

点を作る

OCCの点は、扱うレベルによっていくつか言い方が変わってきます。
ここではトポロジーレベル(TopoDS)の点(Vertex)を作成します。

gp_Pnt p(10, 20, 0);
TopoDS_Vertex v = BRepBuilderAPI_MakeVertex(p);

簡単ですね!

上の例では、座標値 [10, 20, 0] の位置に存在する点を作成しました。
gp_Pnt は、これも点と言えば点なのですが、主に X, Y, Z の座標値を格納したり、引数に渡す時の入れ物としての「点」です。
一方、TopoDS_Vertex は、幾何として三次元空間上に存在する「点」です。

なんだか良く分からない説明ですが、gp_Pnt をその人のプロフィール、TopoDS_Vertex がその人自身、みたいな感じで見てもらったら結構です。(かえって分かりづらいかな…)

通常は上の例で問題がないんですが、もう少しだけ「ちゃんと」記述すると、次のようにも書けます。

gp_Pnt p(10, 20, 0);
BRepBuilderAPI_MakeVertex mv(p);
TopoDS_Vertex v = mv.Vertex(); // Vertex として受け取る場合
TopoDS_Shape  s = mv.Shape();  // Shape として受け取る場合

BRepBuilderAPI_Make~系のクラスは、作る目的の型に特殊化してくれるメソッドと、抽象的な TopoDS_Shape 型で返してくれる Shape() が大体備わっています。用途に応じて使い分けができます。

線分を作成する

OCCにおける線という概念も、点と同様に扱うレベルによって言い方が変わってきます。ここでも、まずはトポロジーレベルでの線分を作成してみましょう。

gp_Pnt start_point(0, 0, 0);
gp_Pnt terminal_point(10, 20, 0);
TopoDS_Edge e = BRepBuilderAPI_MakeEdge(start_point, terminal_point);

gp_Pnt 型で始終点を定義して、それを引数に BRepBuilderAPI_MakeEdge() で線分を作成しています。これも Vertex 同様に MakeEdge のオブジェクトを作り

gp_Pnt start_point(0, 0, 0);
gp_Pnt terminal_point(10, 20, 0);
BRepBuilderAPI_MakeEdge me(start_point, terminal_point);
if (me.IsDone()) {
    TopoDS_Edge e = me.Edge();   // Edge として受け取る場合
    TopoDS_Shape s = me.Shape(); // Shape として受け取る場合
}

という風に書く事もできます。Vertex と一つ違うのは、IsDone() メソッドで判定している点です。
Vertex の場合は、点の作成に失敗することがありませんが、線分の場合は例えば始終点が同一点であった場合など、線分として成立しない引数が渡される可能性があります。
そこで、作成が上手くいったかどうかを IsDone() で判定しているわけです。
MakeEdge に限らず、引数によっては成立しない形状を作成する際には、いきなり Shape() を取得するのではなく IsDone() でチェックすることを心掛けましょう。

ポリラインを作成する

ポリラインは BRepBuilderAPI_MakePolygon を使って作成します。トポロジーレベルでの幾何の型は TopoDS_Edge または TopoDS_Wire になります。
作成した形状によって Edge / Wire にいずれかになるわけではなく、次のように用途によって受け取ることができます。

BRepBuilderAPI_MakePolygon mp;

mp.Add(gp_Pnt(0, 0, 0));
mp.Add(gp_Pnt(10, 0, 0));
mp.Add(gp_Pnt(10, 10, 0));
mp.Add(gp_Pnt(20, 10, 0));

mp.Build();

if (mp.IsDone()) {
    TopoDS_Edge e = mp.Edge();    // Edge として
    TopoDS_Wire w = mp.Wire();    // Wire として
    TOpoDS_Shape e = mp.Shape();  // Shape として
}

ポリラインの通過点を順番に Add() していき、最後に Build() します。
点や線分や座標値を1つ、または2つ指定された瞬間にその形状が確定しますがポリラインの場合は複数の通過点によって構成されていますので、明示的に Build() を呼ばないといけません。

オマケ1: TopoDS_Shape からダウンキャストする

発生したシェイプは、とりあえず TopoDS_Shape で受け取っておいて、必要な時にダウンキャストして利用することがよくあります。

キャストと言えば、一般的にはポインタの型変換ですがOCCでは実体アドレスから特殊化する方法があります。(OCCにおけるポインタ隠蔽の仕組みについては別の機会に書きます)

TopoDS_Shape s = ...
TopAbs_ShapeEnum type = s.ShapeType()

if (type == TopAbs_Vertex) {
    TopoDS_Vertex v = TopoDS::Vertex(s);
    ...
}
else if (type == TopAbs_Edge) {
    TopoDS_Edge e = TopoDS::Edge(s);
    ...
}
else if (type == TopAbs_Wire) {
    TopoDS_Wire w = TopoDS::Wire(s);
    ...
}
...

何らかの処理によって発生した TopoDS_Shape 型の s を、その ShapeType を見てそれぞれの型に「ダウンキャスト」しています。
上の TopoDS::Vertex, Edge, Wire のほか、Face, Shell, Solid, CompSolid, Compound, Shape もそれぞれの型に対応して使うことができます。

また、これらの TopoDS_* 型は、すべて TopoDS_Shape の派生クラスになっているので、

TopoDS_Shape s = some_builder_instance.Edge();

と、親の型で受け取っても、必要に応じてダウンキャストできるので問題はありません。
これらの機能により、トポロジーレベルのオブジェクトは柔軟に扱うことができます。

オマケ2: ヘッダーファイルは?

OCCは大抵の場合、使いたいクラスの型名がそのままヘッダーファイル名になっています。
その為、「gp_Pnt」や「BRepBuilderAPI_MakeEdge」を使いたい場合は、

#include <gp_Pnt.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>

のように、拡張子「hxx」をつけて #include してあげればOKです。
もちろん、INCLUDE に OCC の inc ディレクトリが指定しておかなければなりません。