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 の手順と変わりません。

Tiarra やめました。

長らく利用していた IRC プロキシソフト Tiarra の利用をやめました。理由は次のとおりです。

  • tscreen(GNU Screenみたいなの)上の riece で IRC には常駐しているので、端末ログインすればどこからでも繋がる。
    • 携帯電話なんかの専用クライアントから接続できる手軽さはあるけれど、果たしてサーバソフトを常駐させるまで使うかっていうと、そうでもなかった。
    • 携帯電話なんかの専用クライアントから接続する場合は、大人しく別USERとしてログインすればいい気がしてた。
  • 頼んでもない大量の WHO リストが1,2分おきに tiarra サーバから出力されるようになって、チャットどころじゃなくなる。
    • サーバメッセージをメインログじゃなくてチャンネルログに流して、かつアクティブを持っていっちゃうようなクライアント側にも問題があるはずなんだけど、なんだか気持ち悪い。XChatやLimechatで接続すると、WHOリストは「プロトコルログ」のような生ログ表示以外には表示されないので、特に問題ない。
    • tiarra を経由しなかったら、riece でも WHOリストの大量取得現象は見られなかったから、IRCサーバ側にリクエストを投げているのは tiarra のはず。ここ1年くらいで、現象が起きる日と起きないがある。設定はいじってないし、なんだろうな。
    • 次の理由により、詳しく調べる前にtiarraの利用をやめることにしました。
  • かなり高度かつ柔軟な設定が出来るわりに、得られる効果がほとんどない。
    • tiarra のメリットと言えば、同一ホストからのセッション数を減らすことができる点。
    • それ以外だと、メイン宛のPRIVMSGを外部から読める、チャンネルの入退室を最小限にできる、常駐ロガーとして活躍…くらいか。
      • 常駐ロガーという意味では、自分のように既にクライアントソフトが常駐しているユーザにとってまるで意味がないもの。
    • デメリット
      • 設定が高度な分、煩雑でもあり、接続ができなくなったり、DCCのルーティング設定で問題があった場合など、問題を複雑化してしまいそう。
      • 複数サーバーに対し、それぞれ違う nick でログインしている/したいのに、そこらへんの設定が考慮されずに設計されていそう。
      • 設定自体、ちょっとWindows寄り。起動スクリプトを書いてcronなどに登録したり、ユーザごとの設定ファイルに対応させたり、いろいろ改造して使ってました。

お世話になっていたのは確かなのですが、IRC を便利に手間なく使うための Tiarra を本気で改良するのも本末転倒になってしまいそうで、結果的に利用をやめてしまいました。

当初、自分が Tiarra を導入した頃はガラケーでしたので、CGI/HTMLベースのIRCクライアントと一緒に使う際に威力を発揮していたんですが、今じゃスマホからSSHで端末を開いて、screen セッション上で動いている riece on emacs を表示して、普通にチャットができますもんね。

なんだかマイナス意見ばかりになってしまいましたが(利用をやめた理由だから当たり前ですが…)、オープンソースでこれだけのものを作って公開してくださった方には感謝しています。

 

tr1の正規表現ライブラリを使う

Microsoft Visual Studio 2008 の SP1 からは C++ Technical Report 1 (TR1)をサポートしているので、Boost やPCRE などのライブラリを用いずに正規表現を扱うことができます。

[cpp]

include <iostream>

include <string>

include <regex>

// usage: a.exe pattern replace-to

using namespace std;

int main(int c, char** v)
{
if (c != 3) return 1;

tr1::regex ex(v[1]); // 正規表現オブジェクト
char buf[256]; // 読み取り用のバッファ

while (cin.getline(buf, sizeof(buf)))
cout << tr1::regex_replace(string(buf), ex, string(v[2])) << endl;

return 0;
}
[/cpp]

Boost のコードと比べれば、名前空間が boost から tr1 に変わったこと、regex ヘッダが標準パスに入ったこと、regex_replace() の第3引数が char* から std::string に変わったことくらいでしょうか。Boost からの移植ですので、互換性があって当然ですね。

C++11 では、名前空間がさらに tr1 から std に移されているようです。TR1 は C++ の言語自体の規格ではないものの、拡張ライブラリの標準規格ですので、名前空間などの移り変わりに注意しておけば、今後も安心して使っていけると思われます。

音声合成エンジン Open JTalk について

動画の生放送サイトを見ていると、リアルタイムに視聴者から来るコメントを合成音声によって読み上げている人が増えているようで、合成音声のジャンルも賑わってきたようです。ボーカロイドの初音ミクが登場するよりも前、AquesTalk をいじってみて調整次第ではかなり「聞ける」音声になるんだなあ、と実感した覚えがあります。 自分は合成音声を入れた動画を作成したり、配信したりするわけではないのですが、IRCのログやTwitterのタイムラインの読み上げ程度には使ってみたくなりました。 ということで、今更感が拭えませんが、Open JTalkを使ってみた時のメモを書いておきます。

Open JTalk とは

オープンソースの日本語音声合成エンジンです。音声合成エンジンには、入力文章の処理部と発音部に機能が分かれるんですが、文章処理にはオープンソースの形態素解析エンジン MeCab を利用しているようです。ChaSen や Kakasi に並んで有名なエンジンですね。発音部には、HMM-based Speech Synthesis System (HTS)というエンジンを利用しているようです。 Open JTalk のデモページでは、WWWブラウザ経由で音声合成を試すことができます。 試しに作ってみた音声を置いておきます。能登っぽい声ですね。 こちらのデモページでは、更新履歴を見る限り、Open JTalk のバージョン 1.06 を利用しているようです。

インストール

インストールした時点での私の環境は Ubuntu 13.04 x64 版です。2013年6月現在、Ubuntu のリポジトリに入っているのはバージョン 1.05 で、公式サイトにて公開されている最新版は 1.06 です。詳しい事は後述しますが、これらのバージョンによって利用できる音声データファイルの形式が違ってきます。

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で表示できるようにしたものですので、読み込みに時間がかかるかもしれません。 ※平面の法線ベクトルの関係で、デモを開いた状態では、ほとんど何も表示されていないかもしれません。マウスの左ボタンで画面を軽くドラッグしてやると、物体が回転してものが表示されるようになるはずです。

OpenCASCADE BRep ファイルの入出力

OpenCASCADE では、標準のファイル形式として OpenCASCADE BRep ファイルというものを用いています。一般的に BREP というと、Boundary REPresentation (境界表現) 自体のことを差しますが、ここでは、「OpenCASCADE で表現できるトポロジー形状を保存するためのファイルフォーマット」とし区別して、BRepファイルと呼ぶことにします。

TopoDS_Shape 型になっているトポロジー形状のファイルへの保存、読み込みは、すごく簡単です。実際にコードを見てみましょう。

[cpp]

include <iostream>

include <TopoDS.hxx>

include <gp_Pnt.hxx>

include <TopoDS_Solid.hxx>

include <BRepPrimAPI_MakeBox.hxx>

include <BRepTools.hxx>

int main(int c, char** v)
{
// ソリッド作成
gp_Pnt opos(0.0, 0.0, 0.0);
BRepPrimAPI_MakeBox box(opos, 10.0, 10.0, 10.0);
TopoDS_Solid solid = box.Solid();

// ファイルへ出力
BRepTools::Write(solid, &quot;test.brep&quot;);

// ファイルから入力
TopoDS_Shape shape;
BRep_Builder bb;
BRepTools::Read(shape, &quot;test.brep&quot;, bb);

std::cout &lt;&lt; &quot;Readed shape type no is &quot; &lt;&lt; shape.ShapeType() &lt;&lt; std::endl;
// TopAbs_SOLID を表す、「2」が表示される

return 0;

}
[/cpp]

今回のキモは、BRepTools.hxxを読み込み、BRepTools::Write() または Read() でファイルパスや TopoDS_Shape オブジェクトを指定している部分です。まあ、ほとんど説明する必要がないと思います。
Write()/Read() は、ファイルパス文字列の他、ストリームを渡すことも可能です。

[cpp]
// 文字列ストリームへ出力
std::ostringstream ost;
BRepTools::Write(solid, ost);
std::cout << ost.str() << std::endl;

// 文字列ストリームから入力
std::istringstream ist;
ist.str(ost.str());
BRepTools::Read(shape, ist, bb);
std::cout << "Readed shape type no is " << shape.ShapeType() << std::endl;
[/cpp]

BRep ファイルの中身は、プレーンテキストです。上の例で出力した内容は次のようになりました。

[cpp]
DBRep_DrawableShape

CASCADE Topology V1, (c) Matra-Datavision
Locations 0
Curve2ds 24
1 0 0 1 0
1 0 0 1 0
1 10 0 0 -1
1 0 0 0 1
1 0 -10 1 0
1 0 0 1 0
1 0 0 0 -1
1 0 0 0 1
1 0 0 1 0
1 0 10 1 0
1 10 0 0 -1
1 10 0 0 1
1 0 -10 1 0
1 0 10 1 0
1 0 0 0 -1
1 10 0 0 1
1 0 0 0 1
1 0 0 1 0
1 10 0 0 1
1 0 0 1 0
1 0 0 0 1
1 0 10 1 0
1 10 0 0 1
1 0 10 1 0
Curves 12
1 0 0 0 0 0 1
1 0 0 10 -0 1 0
1 0 10 0 0 0 1
1 0 0 0 -0 1 0
1 10 0 0 0 0 1
1 10 0 10 0 1 0
1 10 10 0 0 0 1
1 10 0 0 -0 1 0
1 0 0 0 1 0 -0
1 0 0 10 1 0 -0
1 0 10 0 1 0 -0
1 0 10 10 1 0 -0
Polygon3D 0
PolygonOnTriangulations 0
Surfaces 6
1 0 0 0 1 0 -0 0 0 1 0 -1 0
1 0 0 0 -0 1 0 0 0 1 1 0 -0
1 0 0 10 0 0 1 1 0 -0 -0 1 0
1 0 10 0 -0 1 0 0 0 1 1 0 -0
1 0 0 0 0 0 1 1 0 -0 -0 1 0
1 10 0 0 1 0 -0 0 0 1 0 -1 0
Triangulations 0

TShapes 34
Ve
1e-07
0 0 10
0 0

0101101
*
Ve
1e-07
0 0 0
0 0

0101101
*
Ed
1e-07 1 1 0
1 1 0 0 10
2 1 1 0 0 10
2 2 2 0 0 10
0

0101000
-34 0 +33 0 *
Ve
1e-07
0 10 10
0 0

0101101
*
Ed
1e-07 1 1 0
1 2 0 0 10
2 3 1 0 0 10
2 4 3 0 0 10
0

0101000
-31 0 +34 0 *
Ve
1e-07
0 10 0
0 0

0101101
*
Ed
1e-07 1 1 0
1 3 0 0 10
2 5 1 0 0 10
2 6 4 0 0 10
0

0101000
-31 0 +29 0 *
Ed
1e-07 1 1 0
1 4 0 0 10
2 7 1 0 0 10
2 8 5 0 0 10
0

0101000
-29 0 +33 0 *
Wi

0101000
-32 0 -30 0 +28 0 +27 0 *
Fa
0 1e-07 1 0

0111000
+26 0 *
Ve
1e-07
10 0 10
0 0

0101101
*
Ve
1e-07
10 0 0
0 0

0101101
*
Ed
1e-07 1 1 0
1 5 0 0 10
2 9 6 0 0 10
2 10 2 0 0 10
0

0101000
-24 0 +23 0 *
Ve
1e-07
10 10 10
0 0

0101101
*
Ed
1e-07 1 1 0
1 6 0 0 10
2 11 6 0 0 10
2 12 3 0 0 10
0

0101000
-21 0 +24 0 *
Ve
1e-07
10 10 0
0 0

0101101
*
Ed
1e-07 1 1 0
1 7 0 0 10
2 13 6 0 0 10
2 14 4 0 0 10
0

0101000
-21 0 +19 0 *
Ed
1e-07 1 1 0
1 8 0 0 10
2 15 6 0 0 10
2 16 5 0 0 10
0

0101000
-19 0 +23 0 *
Wi

0101000
-22 0 -20 0 +18 0 +17 0 *
Fa
0 1e-07 6 0

0111000
+16 0 *
Ed
1e-07 1 1 0
1 9 0 0 10
2 17 2 0 0 10
2 18 5 0 0 10
0

0101000
-23 0 +33 0 *
Ed
1e-07 1 1 0
1 10 0 0 10
2 19 2 0 0 10
2 20 3 0 0 10
0

0101000
-24 0 +34 0 *
Wi

0101000
-14 0 -22 0 +13 0 +32 0 *
Fa
0 1e-07 2 0

0111000
+12 0 *
Ed
1e-07 1 1 0
1 11 0 0 10
2 21 4 0 0 10
2 22 5 0 0 10
0

0101000
-19 0 +29 0 *
Ed
1e-07 1 1 0
1 12 0 0 10
2 23 4 0 0 10
2 24 3 0 0 10
0

0101000
-21 0 +31 0 *
Wi

0101000
-10 0 -18 0 +9 0 +28 0 *
Fa
0 1e-07 4 0

0111000
+8 0 *
Wi

0101000
-27 0 -10 0 +17 0 +14 0 *
Fa
0 1e-07 5 0

0111000
+6 0 *
Wi

0101000
-30 0 -9 0 +20 0 +13 0 *
Fa
0 1e-07 3 0

0111000
+4 0 *
Sh

0101100
-25 0 +15 0 -11 0 +7 0 -5 0 +3 0 *
So

1100000
+2 0 *

+1 0
[/cpp]

onnanoko本日のまとめ

TopoDS_Shape 型のオブジェクトを保存、読み込むには BRepTools::Write() または Read() を用いる。

OpenCASCADE のトポロジーオブジェクト

トポロジーオブジェクトの種類

OpenCASCADE には、次のようなトポロジカルな形状があります。※トポロジーとジオメトリーに関しては、別記事をご覧ください。

occtopodsshapes

  • Vertex バーテックス
    ジオメトリの Point に相当する0次元の形状。
  • Edge エッジ
    始終点に Vertex を持つ曲線や直線。
  • Wire ワイヤ
    連続した Edge の集合。曲線を含む複数のEdgeからなるポリラインや、閉じた輪形状などを表わす。単にEdge が複数連なっているだけではなく、どのEdgeがどのEdgeに連結しているか、という結びつけの情報や、「自身が Closed であるか」(輪形状か)という属性値を持つ。
     
  • Face フェイス
    閉じたWireによって囲まれた曲面。平面も含む。
  • Shell シェル
    Edgeにより連結されたFaceの集合。複数の面から構成されたものや、閉じた空間などを表す。Faceを並べただけではなく、あるFaceは別のFaceと、あるEdgeを共有して連結している、という結びつけの情報を持つ。
     
  • Solid ソリッド
    閉じたシェルによって構成される空間を内と外に分けたもの。空間が分けられることにより、ボリューム(容積)を計算することが可能。シェルを空の箱に例えると、ソリッドは中身が積まった固体
  • CompSolid コンプ・ソリッド
    Faceにより連結された Solid の集合。
  • Compound コンパウンド
    上記、7種類を全て含むことができる「複合体」。種類の違う一つ以上のオブジェクトをまとめる為に利用される形状なので、「形状のグループ」として考えても相違ない。Compound自身もCompoundの中に詰め込むことができる。

それぞれ、プログラムコード中では、TopoDS_Edge や TopoDS_Face というように、TopoDS_ 接頭辞が付きます。また、これらの形状をまとめて Shape (形状)と言い、TopoDS_Shape 型で表わされます。TopoDS_Shape 型は、これらの形状の型の親クラスとなっているので、ワイルドカード的な利用ができます。

occtopodsshape-inheritedmap

TopoDS_Shape 型

OpenCASCADE では、幾何演算の結果をよく TopoDS_Shape 型でユーザに返却します。これは、前述のとおり、点だろうが面だろうが関係なく、漠然と「形状(Shape)」を表している型なので、ユーザが正しく形状を扱うには、Shape の実体として何が詰まっているかを知らなければなりません。そこで、TopoDS_Shape には、ShapeType() というメンバ関数が準備されており、このタイプを見ることによって、TopoDS_Edge や TopoDS_Face に安全にダウンキャストすることができます。

[cpp]
// 何らかの処理で Shape 型の S を受け取る
TopoDS_Shape S = …
if (S.ShapeType() == TopAbs_FACE) { // Face かな?
TopoDS_Face F = TopoDS::Face(S); // Shape を Face にダウンキャスト

}
else …
[/cpp]

ShapeType() の戻り値はTopAbs_ShapeEnum 列挙型で、この列挙型の内容は上で紹介した型と対応しています。また、TopoDS_Shape のそれぞれの派生クラスへのダウンキャストは、TopoDS::Face(S) のように行ないます。

トポロジー・エクスプローラ

上で紹介したとおり、ある形状は子要素となる形状によって構成されています。例えばFaceの場合、Faceを構成しているWireやEdge、Vertexといった子要素が含まれています。これらの親要素と子要素は、ともにトポロジカルな関係を持っていますので、例えば、Edgeの集合(つまりWire)からFaceを作成した後に、Edgeの一部を変更した場合、それに追従してFaceの形状も変化することになります。

ある形状を構成している子要素を取得するには、トポロジー・エクスプローラ TopExp_Explorer を用います。

[cpp]
// ソリッド作成
gp_Pnt opos(0.0, 0.0, 0.0);
BRepPrimAPI_MakeBox box(opos, 10.0, 10.0, 10.0);
TopoDS_Solid solid = box.Solid();

// ソリッドに対し、構成要素のFaceを取得するエクスプローラを作成
TopExp_Explorer ex(solid, TopAbs_FACE);

// Face を取得
for (; ex.More(); ex.Next()) {
TopoDS_Face face = TopoDS::Face(ex.Current());
std::cout << "Face: " << face.HashCode(0xFF) << std::endl;
}
[/cpp]

上記のように、対象物を solid とし、取得したい型の TopAbs_FACE を指定して TopExp_Explorer のインスタンスを作成してあげます。TopExp_Explorer はイテレータ型ではありませんが、イテレータのように振る舞うオブジェクトですので、::More() や ::Next()、::Current() メソッドを使って、構成している子要素を取得していきます。列挙される順番は保証されていないようです。

上記の例では、6 枚の Face を取得することができます。また、取得した子要素 Face に対して、さらに別のエクスプローラ(TopAbs_EDGE を指定)を作成して、Edge を取ってくることもできますし、直に TopAbs_VERTEX を指定すると子要素の Vertex を取得することも可能です。

[cpp]
TopExp_Explorer ex(solid, TopAbs_FACE);
for (; ex.More(); ex.Next()) {
TopoDS_Face face = TopoDS::Face(ex.Current());
// エクスプローラで取得したFaceに対して、今度はFaceを構成するEdgeを取得しに行く
TopExp_Explorer ex_edge(face, TopAbs_EDGE);
for (; ex_edge.More(); ex_edge.Next()) {
TopoDS_Edge edge = TopoDS::Edge(ex_edge.Current());

}

}
[/cpp]

onnanoko本日のまとめ

  • OpenCASCADE のトポロジー形状には、Vertex、Edge、Wire、Face、Shell、Solid、CompSolid、Compaundがある。
  • これらの型は、総じてShape(TopoDS_Shape)として扱われる。
  • Shape オブジェクトの内容物は ShapeType() で判別できる。また、TopoDS::トポロジー形状の名前(TopoDS_Shapeオブジェクト)でダウンキャストができる。
  • トポロジー形状を構成する子要素の取得は、トポロジーエクスプローラ(TopExp_Explorer)を用いる。

自転車プチ探訪: 相浦一周withうどんげルート(約24km)

よくお邪魔する定食屋さんに、うちの使ってないパソコンを譲ることになり、昼過ぎに自宅まで取りに来てもらいました。引き取ってもらった後、電源ケーブルの3ツ穴 to 2ツ穴コネクタを渡し忘れたことに気付き、自転車でお店まで届けることにしました。お店には、ちょうど旦那さんとおかみさんが居て、「良い天気ですね、これからどこに行くの?」と言われたので、何となく「ホームセンターまで買いものに」と答えてしまいました。ホームセンターは大好きなので、行ったら行ったで何か用事が見つかります。ちょうど、自転車で行ける範囲にあるホームセンターのキャンプ用品の品揃えを確認したかったので、そのまま出掛けることにしました。

電動アシスト自転車を買ってから、干尽のナフコには行ったし、大塔のコーナンは、先日の早岐と同じルートになりそうだったので、今度は日野のOKホーム&ガーデンに行くことにしました。(ちなみに私は、佐世保市役所周辺に住んでいます) OKホーム&ガーデンは、車でも10年以上行っていなかったので、キャンプ用品どころかどんなものが売ってあるかすら知りません。また、前から計画している黒島旅行の下見も兼ねて、SSKバイパス→鹿子前→日野→相浦港の道も見ておきたかったので、向かうべき方向はすぐに決まりました。

DSC01669.JPGDSC01672.JPGDSC01673.JPG

前回の早岐に行った時は忘れてましたが、今回はちゃんとカバンの中にサイバーショットを入れてました。すごく天気が良くて日焼けしそうです。上は、総合病院の前にある旧海軍佐世保鎮守府凱旋記念館とハンバーガーショップの老舗、ヒカリの前です。日曜日の昼下がりなので、いつものように混雑してました。

DSC01677.JPGDSC01678.JPG DSC01679.JPG DSC01680.JPG

米軍佐世保基地周辺です。オートフォーカスだと、フェンス越しの風景が上手く撮れませんなぁ…。もう少し頑張れば遠景に焦点を合わせたまま、引けたのかもしれませんが、2枚で諦めました。SSKバイパスを走っていると、どんどん歩道が狭くなってかなり走るのが大変でした。風景を見ながらゆったり走る暇もありません。
バイパスの終わりのところにあるセブンイレブンで、給水と簡単な昼食(鶏のからあげ1個)を済ませて、鹿子前トンネルに入っていきました。普段、生身の状態でトンネルを抜けることがめったにないので、久々に驚きましたが、トンネル内ってかなりうるさいですね。ちなみに、自転車に標準装備されているオートライトも正しく動いてくれていました。地味に便利です。

DSC01681.JPG DSC01682.JPG DSC01683.JPG DSC01688.JPG DSC01689.JPG

トンネルを抜けると、そこはヨットハーバーでした!良いお天気だったので、広場で親子連れが遊んでいたり、シーカヤックに乗った人がいたりと気持ちいい感じでした。同僚の方がこの近くにマンションを買ったらしく、市街地から自転車で行ける距離&素敵なリゾート地でいいなあ、と改めて実感。

ここで、西海パールシーリゾートの対岸にある半島の公園で、トビカズラの花が咲いているニュースを思い出しました。実は、連休中にも母と車で公園の前まで行ったのですが、駐車が出来なかったので結局見れず終いだったので、ついでに見に行くことにしました。

DSC01690.JPG DSC01692.JPG

公園は「長尾半島公園」という名前で、1枚目の写真の向かって右側の半島です。木がわさわさ茂っているだけの部分。左側のコの字のゲートが並んでいるところがパールシーですね。

小さな港町を抜け、住宅の路地を進んだ突き当たりに長尾半島公園はありました。

DSC01693.JPG DSC01694.JPG

駐車場らしきスペースはあるものの、車止めでしっかりガードされています。公園に至る道にも、何箇所かに「車では行けないので、パールシーから徒歩で!」という看板が目立ちました。公園の前の道が狭い住宅に面した路地なので、近隣住民に配慮してのことでしょうね。バイクや自転車の乗り入れもしっかりと禁止されていましたので、自転車はここでお留守番です。

DSC01695.JPG DSC01696.JPG DSC01700.JPG DSC01702.JPG

公園内は、意外と綺麗に遊歩道が整備してありました。入口のベンチでおじさんが寝転がっていたのを除けば、自分一人だけでした。ところどころ、育成を観察している旨の札が立てられていました。散歩して歩くのは気持ちいいですが、まあ、何もないと言えば何もない。

DSC01709.2

目的のトビカズラが見当たらないまま、突き当たりの展望台まで来てしまいました。ついでなので、パノラマ撮影でパシャリ。どこにあるのかなー、と注意深く来た道を戻ってみると、東屋になっているところにありました。

DSC01717.JPG DSC01715.JPG DSC01710.JPG

「あったー!…けど思ってたより地味ー!」と思いました。ついでに言うと、花が少ないです。足もとにちょっとしかありません。最初はなんかの雑草が茂っているだけかと思いました。ちなみにこの花は、日本では九十九島のトコイ島と熊本の2ヶ所にしか自生していない珍しい花で、「数百年に一度しか花を咲かせない『うどんげ』の花」とも言われている、ありがたい花だそうです。うどんげにしては地味です。

目的のモノも拝めたので、来た道を戻ってホームセンターに行くことにしました。日野のOKホーム&ガーデンでシュラフが4種類ほどあったので、最低使用温度15度のものを購入。オールシーズン使える厚いシュラフは、既に自宅にあるんですが(しかも3つも)、自転車を積むことを考えて、コンパクトで軽いものを購入しました。ここで、自宅を出発してから8kmほど。サイクリングコンピュータは1500円でもすごく役に立ってます。

さて、次の目的地、相浦港に行くことにします。日野のララプレイスを通り、自動車学校の前を北上していると、この辺りから土地勘がなくなってきました。とりあえず、看板に出ていた陸上自衛隊佐世保駐屯地の方を目指すことに。

DSC01719.JPG

陸自脇の道から撮影した写真です。手前の自動車学校みたいな土地も多分自衛隊施設です。自衛隊に入ると普通免許に加え、いろんな免許が取れるらしいですが、ここで訓練しているんでしょうかね。脇の道を北上していくと、どんどん道が狭くなり、何故か山を登り始めました。

DSC01721.JPG

ちょ、港は海に面しているんじゃ…と思いつつ、ひたすら坂を登ります。あの曲り角を越えれば下り坂だろう、と、3回ほど思って、3回ほど裏切られました。結構登ってるよコレ!目の前には海どころか、登り坂と空と住宅地しか見えません。…自転車を押さないと登らないくらいの斜面を登りきったところに、垣根の剪定をしているおじさんが居たので、港までの道を聞きました。すごく丁寧に親切に教えていただきました。どうやら今来た道を戻らなくて済みそう。ありがたやありがたや!

ちなみに、上の写真のリアキャリアにくくりつけてあるのがホームセンターで購入したシュラフ。さっそく自転車にくくりつけてみたかったので、箱はお店で捨ててもらいました。

DSC01723.JPG DSC01724.JPG DSC01725.JPG

おじさんの言うとおりに道を進むと、無事、相浦港に到着することができました。こう言うとアレかもしれませんが、田舎の小ぢんまりした漁港っていいですよね。子供たち7、8人が自転車に乗って港をうろうろしていました。すごく平和だ。3枚目の写真は、黒島行きのフェリーが出ている待合所です。このターミナルの向かいに小さな商店と宿がありました。

さて、次の目的地は…と考えていると、結構疲れがきていました。走った距離は10kmちょっとでしたが、公園や店内をウロウロしたり、走行中も強い日射しの下だったりと体力の消耗が思ったよりあったようです。しかし、今来た道を戻るのもなんだかアレだし、時間もまだ午後3時だったので、勇気を出して(?)大野経由で市役所前まで帰ることにしました。

DSC01726.JPG DSC01727.JPG DSC01728.JPG

相浦大橋の上を走っていると、左手には愛宕山、右手には先ほどの港が見えました。長崎県立大学の脇を抜け、途中にあったダイソーに寄って、ひたすら大野方面を目指します。本山のモスバーガーの隣にあるガーデニングショップのグリーンズに立ち寄り、赤ベタ(熱帯魚)を1匹購入。ベタを傷つけないようにゆっくり走りました。その後、すき家で昼食(2回目)を取ってから、道なりに南下しました。相浦から大野までって、結構距離があるはずなのに何もない感がすごいです。そんな事言うと怒られてしまうか…。

DSC01730.JPGDSC01732.JPG DSC01733.JPG

途中、今宮神社というのがあったのでちょっとだけ自転車をとめて見てました。参拝はしていません。
大野から市役所の方へ帰って行く途中、物凄い勢いで追い抜いていったロードバイクの人がいました。ウェアから何からガチの方です。50km/sくらい出てそうだったので、さすがに車道を走っていました。ちなみに自分の自転車は、アシストナシの状態で、下り坂で40.5km/sが出たのが最高です。こんだけ重かったら、さもありなんといった感じ。

DSC01738.JPG

そんなこんなで、懐かしの市役所周辺まで戻ってきました。もう日が傾いてますね。サイクリングコンピュータによると、全行程で24.62km。早岐往復が24.97kmだったので350mほど今回は短い旅だったようです。学生時代、新聞配達の奨学金制度を利用していた為、配達用や通勤用自転車(シティサイクル)で毎日50km以上走ってたので、自分も当時の体型、かつロードバイクなんて持ってたら、5、60kmくらい平気で走れたんだろうなぁ、と思いました。まあ、今後の目標にしてもいいか。以前持っていた(盗難されてしまった)クロスバイクでは考えもしなかった行動範囲の移動ができるので(しかも膝を痛めず!)、電動アシストと言えど動かないよりマシなはず。

DSC01739.JPG

自分の電動アシスト自転車「ビビ・ストロング」です。パナソニック製で、設計荷重120kgというかなり強靭な仕様。クロスバイク風のハリヤーなんかと迷いましたが、普段使いでも長く使いたかったので、頑丈かつ重たい車体を選んでいます。シティサイクルにパニアバッグをつけてるので、見る人が見たら結構滑稽に見えるのかもしれませんが、この無骨なフレームに秘められた輸送能力(だけ)は、ロードバイクなんか目じゃないぜ!と思いたいです。(なので、すごく理にかなった形なのです。きっと)

ちなみに、ロードバイクなら車体重量は7、8kg、クロスバイクでも10kg前後なご時世ですが、このビビストロングの重量は33.9kgもあります(何も積んでない状態で)。丈夫にする→重くなる→パワーアップ→重くなる…の負の連鎖が見えますね(苦笑)。
まあ、アシストを完全にオフにしても走れなくなるほど重くはないので、車体を軽くするより自分の体重を数kgでも落とした方が経済的だと思いました。

 DSC01743.JPG DSC01745.JPG DSC01747.JPG  DSC01753.JPG DSC01754.JPG

バッテリーは250ワットの12アンペア。ギアの奥にモーターを搭載しています。フロントライトはLEDの自動点灯機能付き、リアライトはソーラー発電で、これも自動点灯点滅仕様です。頑丈に作ってあるせいか、標準装備の前後キャリアがすごく重そうです。今の時点では、サイクリングコンピュータをつけたくらいでほぼ無改造のまま走っていますが、いかにもママチャリっぽいフロントキャリアは外して、リアはパニアバッグ用のキャリアに変更しようと思っています。

さあ、次は本当の旅が出来ればいいなぁ。

DSC01759.JPG

あ、忘れてましたが、前回の早岐神社に行った時に買ったお守りは自転車のキーにつけてます。

自炊ZIPをスマホや電子ブックリーダー向けに最適化する

去る2月、楽天の Kobo を中古購入しました。投げ売り価格の2500円でした。私は風呂でよく本を読むので、浴槽に落としてもそんなに凹まない安価な端末を探していました。期待をまるでしていない、という意味では Kobo は最高の端末です。

2500円という価格にしては、Linuxが中で動いているし、電子ブックリーダーでしか触ることのできない電子ペーパーを扱えたり、WiFiに対応していたり、本体を開ければ簡単にカスタマイズできたり、と Raspberry Pi よりも「香ばしい」端末なんですけどね。あんなに薄いのにバッテリーまで搭載しているし。

ソフトウェア面は最悪です。電子ブックリーダーなのに本が読みにくい。本棚の表示が遅かったり、改ページが遅かったり。電子ペーパーの画素書き換えの遅さではなく、それ以上にソフトウェアが下手に組んである遅さが気になりました。20世紀末の Windows 98 全盛期くらいの最新 PC くらいのスペックは持ってるんだし、ブックリーダーに用途は特化されているんだから、「もうちょっとどうにかしてよ」という感じ。サービス面に至っては、今更論評するまでもなしですね。

Kobo は至るところで散々叩かれまくっているので、用途を割り切るつもりで購入しました。割り切って使うと決めて購入したら、現物を触ってみてもそんなに落差がないです。
「ああ、やっぱ遅いな」とかそのくらいの印象でした。同時に、「割り切って使う」という目的はなんとかクリアできそうという実感も。

hangousuihan

hangousuihan は、画像ファイルを含むZIP圧縮ファイルに対し、解凍、画像処理、再圧縮の一連の処理を行うツールです。以前、Windows 向けに書きました。このツールの目的は、スマートフォンやタブレットPC、電子ブックリーダーなどの端末の画面にジャストフィットするサイズに画像を縮小して、圧縮ファイル自体をコンパクトにすることです。超高解像度のマンガファイル、1冊300MBなんていうのは家に保存しておいて、実際に見るのは30MBくらいのファイルで充分ですよね、というアプローチです。私が持っている Kobo も、micro SD カードを差すことができますが、水没を前提と考えているので、あまりお金をかけて拡張する気がありません。内蔵メモリだけで済ませたいところです。

Windows の場合、カチッとしたアプリケーションを拵えない限り使いものにはなりませんが、UNIX 系 OS の場合はスクリプトを書けば事足ります。私は普段、Linux しか使ってませんので、次のシェルスクリプトを用いています。

[bash]

!/bin/bash

wd="/tmp/$$"

for target in "$@"
do
[ ! -d "$wd" ] && mkdir "$wd"
unzip -j "$target" -d "$wd"
[ ! -d "$wd/result" ] && mkdir "$wd/result"
tname=basename &quot;$target&quot;
for item in find &quot;$wd&quot; -type f
do
name=basename &quot;$item&quot;
convert -type grayscale -quality 40 -resize 600×800 "$item" "$wd/result/$name"
zip -9 "/tmp/$tname".cbz "$wd/result/$name"
done
mv "/tmp/$tname".cbz /media/$USER/KOBOeReader/
rm -rf "$wd"
done
[/bash]

Kobo の画面解像度にフィットする 600×800 に画像を圧縮し、グレイスケール化しています。Ubuntu では、Kobo のファイルシステムのマウント場所は /media/$USER/KOBOeReader となるので、そこにダイレクトに CBZ で出力するようにしています。

使い方は、

[bash]$ ./hangousuihan.sh comic01.zip comic02.zip comic03.zip[/bash]

のように、変換したいファイルパスを引数に与えてやればOKです。

nfsをやめてsshfsに一本化

うちの自宅LANには、普段からLinuxサーバとLinuxのラップトップだけしか利用していないので、いわゆる「Windowsネットワーク」というものが存在しません。ファイルの共有はNFSというSunが大昔に開発したUNIXファイル共有ネットワーク環境を、ネットワーク・ログインはNIS認証サーバ(yellowpage)を利用していました。

で、ここにきて以下の理由からNFSをやめてSSHFSと思いました。

NFSから切り替える理由

  1. 古い。1984年生まれなので、私より年上です。UNIXの世界では、「古い」というのは良い意味でも悪い意味でも使われますが、後者の方です。規格がRFCに取りまとめられ、かなり前から標準化が行なわれていますが、最新の NFS version 4 (RFC3530)でも制定れたのは今から10年前の2003年。セキュリティの向上や性能の向上も計られつつも、移り変りの早いコンピュータ業界の流れに着いていけてない感が否めないです。
  2. 1に関係してくる事ですが、最近では
    • 自宅でも無線LAN環境が普通
    • 出先でも3GやLTE回線を用いた通信環境が普通
    • 職場の環境がWindows。SFU?何それ?
    • AndroidやiOSなど、色々なモバイル端末も一般化

    てなご時世ですので、通信の品質があまり良くない環境からも使うことが多くなりました。NAT越えのマウントとかも当たり前ですしね。

  3. それで、例えば、マウントしているクライアントが音信不通になったり、ちょっと不安定になるとnfs-kernel-serverを再起動してやらないといけないとか、パーミッションのちぐはぐ感であったりとか、ユーザランドでの使い勝手が悪い点、NAT越えをさせてもセキュリティ的にどうなの、などなど…そういうところが不便です。「磐石なシステム」を念頭に設計されているシステムは、NFSに限らず古くからあるUNIX系のソフトには多いことです。
  4. あ、あとサーバ側もexportsを管理してやったりと、設定がちょっと面倒ですね。AndroidなんかのLinuxカーネルは、nfs-clientモジュールが入ってなかったりと、クライアント側の負担もちょっとあります。
  5. その点、SSHFSは活発にメンテナンスが行なわれていますし、セキュリティ面はSSH本体に丸投げ、SSHd自体は常に稼動させているくらいスタンダードなデーモンなのでよし、fuseと絡めてユーザランドでの使い勝手も良いし、気になっていた負荷や速度面でも、試してみると問題がないようです。

挙げ出すとキリがないかもしれませんが、「これまでお世話になりました。」という気持ちでそっと remove しました。