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)を用いる。

OpenCASCADE でソリッドのブーリアン演算(とボリューム計算)を行う

OpenCASCADE でソリッドモデル同士のブーリアン演算とボリューム計算を行うサンプルコードを書いてみました。
コードは次のとおり。

#include <stdio.h>

#include <TopoDS.hxx>
#include <gp_Pnt.hxx>
#include <TopoDS_Solid.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepAlgoAPI_Common.hxx>
#include <BRepGProp.hxx>
#include <GProp_GProps.hxx>

int main(int c, char** v)
{
    // s1 というソリッドを作成
    gp_Pnt opos1(0.0, 0.0, 0.0);
    BRepPrimAPI_MakeBox box1(opos1, 10.0, 10.0, 10.0);
    TopoDS_Solid s1 = box1.Solid();

    // s2 というソリッドを作成
    gp_Pnt opos2(2.0, 3.0, 5.0);
    BRepPrimAPI_MakeBox box2(opos2, 10.0, 10.0, 10.0);
    TopoDS_Solid s2 = box2.Solid();

    // ブーリアン演算オブジェクトを s1 と s2 で作成
    BRepAlgoAPI_Common bo(s1, s2);

    // ブーリアン演算オブジェクトのオペレーションを指定
    // BOP_COMMON:  s1 と s2 の共通部分を得る
    // BOP_FUSE:    s1 と s2 を結合したものを得る
    // BOP_CUT:     s1 を s2 でカットしたものを得る
    // BOP_CUT21:   s1 で s1 をカットしたものを得る
    // BOP_SECTION: s1 と s2 のセクションを得る
    bo.SetOperation(BOP_CUT);

    // 計算の実行
    bo.Build();

    // エラーが発生していなかったら
    if (!bo.ErrorStatus()) {
        TopoDS_Shape S = bo.Shape();

        // GPropsオブジェクト(ボリュームや重心位置を得るツール)を作成
        GProp_GProps gprops;
        BRepGProp::VolumeProperties(S, gprops);
        Standard_Real vol = gprops.Mass(); // ボリュームを得る

        printf("volome: %f\n", vol);
    }

    return bo.ErrorStatus();
}

BRepAlgoAPI_Common クラスは、名前に Common の文字がありますが、上のとおり setOperation() で指定すれば、コモン演算(共通部分を得る)だけではなく、カットしたり結合したりすることもできます。

ビルド方法は次のとおり。

g++ -g -lTKernel -lTKMath -lTKBO -lTKTopAlgo -lTKPrim -L/usr/lib/opencas -I/usr/include/opencascade -o solid solid.cxx

OpenCASCADE で IGES ファイルを読み込む

OpenCASCADE で IGES ファイルを読み込むには、IGESControl_Reader クラスを用います。

[cpp]

include <stdio.h>

include <Standard_TypeDef.hxx> // Standard_Boolean

include <Standard_Macro.hxx> // Handle()

include <TopAbs_ShapeEnum.hxx> // ShapeType

include <TopoDS.hxx> // TopoDS::

include <TopoDS_Shape.hxx> // TopoDS_Shape

include <TopoDS_Face.hxx> // TopoDS_Face

include <IGESControl_Reader.hxx> // IGES Reader

int main(int c, char** v)
{
Standard_CString path = "sample28.igs";

// IGES制御リーダで読み込む
IGESControl_Reader reader;
if (reader.ReadFile(path) != IFSelect_RetDone) {
    // エラーが起きた
    reader.PrintCheckLoad(Standard_True, IFSelect_ItemsByEntity);
    return 1;
}

// 変換対象のルート数
printf(&quot;number of roots = %d\n&quot;, reader.NbRootsForTransfer());

// IGES内のルートを OCC で扱えるように変換
reader.TransferRoots();

// 得られたシェイプ数
printf(&quot;number of shapes = %d\n&quot;, reader.NbShapes());

// シェイプの取得
for (int i=1;i&lt;=reader.NbShapes(); i++) {
    TopoDS_Shape s = reader.Shape(i);
    if (s.ShapeType() == TopAbs_FACE) {
        TopoDS_Face f = TopoDS::Face(s);
        // 取得した Face で何かする
        printf(&quot;%04X\n&quot;, f.HashCode(0xFFFF));
    }
}

// 一つのシェイプにして返す場合
// TopoDS_Shape s = reader.OneShape();

return 0;

}
[/cpp]

次のようにビルドします。TKIGES にリンクするのを忘れないように注意してください。

[bash]
$ g++ -g -lTKernel -lTKMath -lTKBinL -lTKIGES -L/usr/lib/opencas -I/usr/include/opencascade -o occtest occtest.cxx
[/bash]

IGES関連のクラスについての解説は、$CASROOT/../doc/iges.pdf にドキュメンテーションが同梱されているので、そちらを参考にしてください。また、IGES ファイル自体の仕様(PDF)は、US Product Data Association で公開されています。

OpenCASCADE 6.5.4 を Microsoft Visual Studio でビルドする

2012年11月13日にリリースされた OpenCASCADE 6.5.4 を Microsoft Visual Studio でビルドする方法を忘れないうちにメモしておきます。

OpenCASCADE で単純に開発したいだけなら、バイナリパッケージのインストールだけで十分ですが、次のような場合は自前でビルドする必要があります。

  • Microsoft VIsual Studio 2008 以外のバージョンで開発したい。
  • OpenCASCADE 本体のソースコードを追いかけてデバッグしたい。

1. 必要なものを取得する

OpenCASCADE の公式ページDownload Center より、インストーラパッケージを取ってきます。ユーザ登録が必要ですので、登録後、公式ページにログインしてからダウンロードしてください。

ビルドに必要なパッケージは、OpenCASCADE654.exe だけです。また、開発にはクラスリファレンスも必要になってきますので、OCCTDocumentation654.exe も同時にダウンロードしておくべきでしょう。

OpenCASCADE654.tar.gz は、バイナリを含まないソースコードパッケージです。こちらの構成には、Visual Studio のソリューション・プロジェクトファイルの他に makefile も同梱されているので、非 Visual Studio 環境で開発する場合はこれを利用します。(今回は Visual Studio ですので割愛します。)

ちなみに、私の環境は Microsoft Windows 7 Professional 日本語版 32ビット、Microsoft Visual Studio 2008 SP1 です。

2. セットアップ

OpenCASCADE654.exe を実行すると、セットアップ・ウィザードが表示されます。インストールパスは、標準では「C:\OpenCASCADE6.5.4」となっていますが、任意の場所にインストールして構いません。(ただし、念の為にパス中に空白文字やワイド文字が入らない場所が無難でしょう。) 私の場合は、複数のバージョンの OpenCASCADE を使い分けているので「E:\opt\occ\654」にインストールすることにしました。この例でもこのパスを利用します。

次にインストール構成を設定します。「Custom installation」を選択するとインストールするものにチェックを入れて選択できるようになります。OpenCASCADE を利用した開発に最低限必要なのは、「Open CASCADE Technology」の「Binary Files」のみです。この Binary Files には、「Release」構成でビルドされた DLL ファイルの他、インクルードヘッダ、ライブラリファイルも含まれています。

今回は、自前で「Debug」構成のビルドをしたいので、バイナリに加えて「Source Files」も選択しておきます。「Documentation」は、先ほどダウンロードした OCCTDocumentation654.exe に入っているものとは違います。先ほどのファイルはクラスリファレンスですが、こちらのドキュメンテーションは、OpenCASCADE 全体について丁寧に解説してある PDF ファイルになります。有用な情報がたくさん入っているので、これもインストールした方がいいと思います。

また、サンプルも実際に動作する有用なコードがたくさん入っているので、自分の環境にあったものを入れておくと良いでしょう。

3. 簡単なディレクトリ構成の説明

インストールが終わると、指定した場所以下に全てのファイルが展開されています。ここでは「E:\opt\occ\654」を指定したので、そのフォルダを開くと以下のようなディレクトリが展開されていると思います。

 3rdparty ... OpenCASCADE が利用しているサードパーティ製ライブラリ群
 data     ... サンプルとして使える CAD データファイル
 doc      ... ドキュメンテーション
 ros      ... OpenCASCADE の本体が入ってるフォルダ
 samples  ... サンプルプロジェクト

ドキュメンテーションやサンプルをインストールしなければ、いくつか存在しないフォルダがあるかもしれません。

ソースコードやプロジェクトファイルが入っているのは、ros フォルダの下になります。この ros フォルダが OpenCASCADE を用いた開発の基点となるフォルダですので、以後 CASROOT と記載します。(OpenCASCADE では、この ros フォルダへのフルパスを環境変数 CASROOT として定義して参照しています。OpenCASCADE 関連のドキュメントで CASROOT という表現が出てきたら ros フォルダのことだと思ってください。)
CASROOT の内容は次のとおり。

 adm        ... プロジェクトファイルが入っているフォルダ
 drv        ... クラスの定義ファイルが入っているフォルダ
 inc        ... インクルード・ヘッダが入っているフォルダ
 src        ... ソースファイルが入っているフォルダ
 win32      ... win32 向けのバイナリとライブラリファイルが入ってるフォルダ
 custom.bat ... 開発者の環境に合わせて環境変数などの定義を変更するためのスクリプト
 draw.bat   ... Draw Test Harness を起動するためのスクリプト
 env.bat    ... OpenCASCADE が必要とする環境変数を定義するスクリプト
 msvc.bat   ... ビルドに必要な環境変数を設定し、Visual Studio を起動するためのスクリプト

それぞれのバッチファイルは、内部で env.bat を呼び出しています。OpenCASCADE のサンプルを実行したり、ビルドに必要な環境変数は env.bat や msvc.bat をテキストエディタで開いてみると分かると思います。

4. ビルド

以前のバージョンでは、日本語 Windows 環境では修正をしないとビルドが通りませんでしたが、今回の 6.5.4 は、そのままの状態でビルドが通るようになっています。

前述のとおり、OpenCASCADE をビルドする為に様々な環境変数を利用しています。そのため、adm フォルダ以下にあるソリューションファイル(.sln)またはプロジェクトファイル(.vcproj)をそのまま開いてもビルドする事ができません。

そこで、環境変数を設定して Visual Studio を起動してくれるスクリプト msvc.bat を実行します。

まず、コマンドプロンプトを起動して CASROOT に移動します。

> cd /d E:\opt\occ\654\ros

次に、msvc.bat を引数付きで実行します。

> msvc.bat vc9 win32 Debug

引数の意味は始めから、Visual Studio のバージョン、ビルド対象のプラットフォーム、ビルドの構成の順に指定します。私の環境は Visual Studio 2008 ですので、第1引数に vc9 を指定しています。vc8 が 2005、vc10 が 2010 になります。紛らわしいので注意が必要です。
第2引数には win32 または win64 が指定できます。第3引数は、「d」もしくは「Debug」を指定しないと Release 構成(デバッグシンボルとかがついていないバイナリを生成)になります。
Release 構成でビルドしたバイナリは、セットアップの手順で「Binary Files」を選択しておけば、自前でビルドしなくても最初からインストールされています。今回はデバッグシンボル付きの Debug 構成バージョンが欲しいので、Debug 構成でビルドすることとします。

※以前のバージョンでは、Visual Studio のインストールパス(デフォルトで C:\Program Files 以下)に空白文字が入っていると、msvc.bat から起動できないという致命的なバグがありましたが、本バージョンでは修正されているようです。

Visual Studio が起動したら、OCCT.sln というソリューションファイルが開かれていることを確認します。このソリューションファイルの実体は %CASROOT%\adm\msvc\vc9\OCCT.sln です。このソリューションには、OpenCASCADEの全67プロジェクトが属していますので、あとは「ソリューションをビルド」するだけでOKです。フルビルドには結構時間がかかりますので、お茶でも呑みながらまったり待ちます。

5. 確認

エラーが発生せずに、すべてのビルドが完了したら成功です。ビルドの成果物は、%CASROOT%\win32\vc9 以下に出力されます。

 bin  ... Release ビルドのバイナリファイル(*.dll;*.exe)
 bind ... Debug ビルドのバイナリファイル(*.dll;*.exe)とデバッグシンボル(*.pdb)
 lib  ... Release ビルドのライブラリファイル(*.lib)
 libd ... Debug ビルドのライブラリファイル(*.lib)
 objd ... Debug ビルドの中間オブジェクトファイル、不要なので削除可。

bind や libd など、末尾に d がついているフォルダは Debug ビルドを意味しています。

OpenCASCADE を用いたアプリケーションを開発するには、インクルードパスに %CASROOT%\inc を、ライブラリパスに %CASROOT%\win32\vc9\lib または libd を追加すればOKです。また、そのアプリケーションを実行するには、%CASROOT%\win32\vc9\bin または bind に PATH を通します。さらに OpenCASCADE が利用しているサードパーティ製のライブラリ(%CASROOT%..\3rdparty 以下のフォルダ)のバイナリファイルがあるフォルダにも PATH を通す必要があります。

最新 Draw Test Harness の配布

2017-03-27 追記

この記事で紹介している Open CASCADE Technology Draw Test Harness の最新版は、公式配布のパッケージ内にあります。

Download Open CASCADE Technology 7.1.0 | OPEN CASCADE

上記の URL から入手することができます。(無料です。ダウンロードには登録が必要)

詳しい使い方は要望があれば記事にしたいと思います。

(追記終わり)


OpenCASCADE 最新版 6.5.3 の Draw Test Harness (対話的なコマンド実行環境) の Windows 用バイナリパッケージをアップロードしました。Open CASCADE Technology Public License に基づいて配布しています。

ダウンロード: dth653.zip (約21MB)

DTH を実行する為に必要な依存関係も一式入っているので、ダウンロード後、解凍するだけで実行できると思います。OpenCASCADE 本体が依存して利用しているライブラリは次のとおりです。配布ライセンスはそれぞれのものになります。

Tcl/Tk, FreeImage, FTGL, GL2PS, TBB, MSVCランタイム(リリース)

テスト環境とは言うものの、これだけでもかなりの事ができます。 例えば、IGESモデルを三次元空間上に読み込み、任意のBスプライン曲面で切り取った面積を求めたり、ソリッドモデル同士のブーリアン演算を行なったり、曲面に他のオブジェクトを投影したり、オフセットを取ったり・・・etc。 詳しい使い方は、ヘルプコマンドを実行するか、同梱の thug.pdf をご覧ください。

OpenCASCADEで曲線同士の交点計算

Open Cascadeを用いて三次元曲線同士の交点を求めるには、GeomAPIのExtremaCurveCurveを利用します。このエクストリーマクラスは、交点を求める為のクラスではなく、曲線同士の、それぞれ相手との最寄りの点を求める為のクラスです。お互いの最寄りの点が同一点であれば、そこが交点として扱うことができます。

ソースコード

[c]

include <stdio.h>

include <TopoDS_Edge.hxx>

include <BRep_Tool.hxx>

// TKBinL … 明示的にリンクしてやる

include <BRepBuilderAPI_MakeEdge.hxx>

// TKGeomAlgo

include <GeomAPI_ExtremaCurveCurve.hxx>

int main(void)
{
// カーブパラメータの受け取り用
Standard_Real _first, _last;

// エッジ(線分)からカーブを作成
TopoDS_Edge e1 = BRepBuilderAPI_MakeEdge(
gp_Pnt(0, 0, 0), gp_Pnt(10, 0, 0));
Handle(Geom_Curve) c1 = BRep_Tool::Curve(e1, _first, _last);

// もう一つ作成
TopoDS_Edge e2 = BRepBuilderAPI_MakeEdge(
gp_Pnt(4, -5, 0), gp_Pnt(6, 5, 0));
Handle(Geom_Curve) c2 = BRep_Tool::Curve(e2, _first, _last);

// エクストリーマに2つのカーブを入れる
GeomAPI_ExtremaCurveCurve ecc;
ecc.Init(c1, c2);

// 各々の最寄り点を得る
gp_Pnt p1;
gp_Pnt p2;
ecc.NearestPoints(p1, p2); // アドレス参照で受け取り

// 得た点が同一点なら、そこが交点。第2引数はトレランス値。
if (p1.IsEqual(p2, 0.001))
printf("p=(%f, %f, %f)\n", p1.X(), p1.Y(), p1.Z());

return 0;
}
[/c]

ビルド

ビルドオプションは次のとおり。これらのライブラリ以外にもリンクしますが、下記のオプション指定だけで適当に解決してくれます。

[bash]
g++ -g -o hoge -I/usr/include/opencascade \
-lTKernel -lTKMath -lTKBinL hoge.cpp
[/bash]

実行すると、次のように交点の座標を印字します。

[bash]
$ ./hoge
p=(5.000000, 0.000000, 0.000000)
[/bash]

  • 延長線上の交点について
  • 交点が複数ある場合

OpenCASCADEの最低限のサンプル

OpenCASCADE を用いた最低限のサンプルを書いてみます。下記は、2つのベクトルが平行か否かを検証するコードです。Standard_Macro.hxx は不要かも。

[c]

include <stdio.h>

include <Standard_Macro.hxx>

include <gp_Pnt.hxx>

include <gp_Vec.hxx>

int main()
{
gp_Pnt p1(0, 0, 0);
gp_Pnt p2(1, 0, 0);
gp_Vec v1(p1, p2);

gp_Pnt p3(1, 2, 0);
gp_Pnt p4(2, 3, 0);
gp_Vec v2(p3, p4);

if (v1.IsParallel(v2, 0) == Standard_True)
puts("parallel!");
else
puts("not parallel!");

return 0;
}
[/c]

ビルドします。

[bash]g++ -g -o hoge -I/usr/include/opencascade -lTKernel -lTKMath hoge.cpp[/bash]

実行します。

[bash]
$ ./hoge
not parallel
[/bash]

上手く動作している模様。次に、依存関係を調べてみます。

[bash]
$ ldd hoge
linux-gate.so.1 => (0xb77a0000)
libTKernel-6.3.0.so => /usr/lib/libTKernel-6.3.0.so (0xb7549000)
libTKMath-6.3.0.so => /usr/lib/libTKMath-6.3.0.so (0xb73f2000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb72fc000)
libm.so.6 => /lib/i686/cmov/libm.so.6 (0xb72d6000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb72b8000)
libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7171000)
libdl.so.2 => /lib/i686/cmov/libdl.so.2 (0xb716d000)
libpthread.so.0 => /lib/i686/cmov/libpthread.so.0 (0xb7153000)
/lib/ld-linux.so.2 (0xb77a1000)
[/bash]

使っていない直接参照を調べます。

[bash]
$ ldd -u hoge
Unused direct dependencies:

    /usr/lib/libTKMath-6.3.0.so
    /usr/lib/libstdc++.so.6
    /lib/i686/cmov/libm.so.6
    /lib/libgcc_s.so.1

[/bash]

あれ、TKMath は使われてないな。っと思ってビルド時に -lTKMath を外すと

[bash]
/tmp/ccSaJdhZ?.o: In function gp_Vec::Angle(gp_Vec const&amp;amp;) const':
/usr/include/opencascade/gp_Vec.lxx:111: undefined reference to
gp_VectorWithNullMagnitude?::Raise(char const*)’
/usr/include/opencascade/gp_Vec.lxx:114: undefined reference to `gp_Dir::Angle(gp_Dir const&amp;) const’
collect2: ld returned 1 exit status
[/bash]

gp_Vec クラスの依存関係の解決に失敗して ld から怒られます。

間接参照で、TKMath も使ってるってことかな。

んー、どいつとリンクさせればいいのか、いまいちちゃんと分かっていない感じです。

bsplinecurve コマンド引数( knot 値)を相互変換

NURBS 曲線の knot 値( Bスプライン基底関数を決定する為の値 )は、一般的に下記のように ( 階数 + 制御点数 ) 個の単調増加する配列として表されます。

T = [ 0 0 0 0 1 2 3 3 3 3 ]

一方、OpenCASCADE Draw Test Harness の bsplinecurve コマンドにて上に示す knot 値を持つ NURBS 曲線を生成するには、下記のように指定する必要があります。

0 4 1 1 2 1 3 4

太字部分は knot 値自体を示すのではなく、”直前にある knot 値をくりかえす回数” を示します。Draw Test Harness の公式リファレンスでは誤った記載で引数の解説がなされていた為、少し迷いました。仮に、前者を Plain knots array、後者を Compressed knots array と呼ぶと、下記のような相互変換を行う Perl スクリプトが組めます。

#!/usr/bin/env perl

# Plain knots array to Compressed knots array
# for Draw Test Harness of OpenCASCADE

sub pKnot2cKnot {
  my @pKnot = @_;
  my @cKnot = ( );
  my $bfr = -1, $nb = 0, $i = 0;
  foreach $val ( @pKnot ) {
    if ( $val != $bfr ) {
      $cKnot[$i++] = $nb if ( $i &gt; 0 );
      $cKnot[$i++] = $bfr = $val;
      $nb = 1;
    }
    else {
      $nb++;
    }
  }
  $cKnot[$i] = $nb;
  return @cKnot;
}

# Compressed knots array to Plain knots array
sub cKnot2pKnot {
  while ( length( $val = shift( @_ ) ) ) {
    $buf .= ( "$val " x ( shift( @_ ) ) );
  }
  return split( " ", $buf );
}

このような仕組みを使って、ある三次元 CAD 形式のファイルから Draw Test Harness 経由で、BRep モデルを作成する事が出来ました。ひょっとしたら pKnot2cKnot() は、もう少しスマートに出来るかも。

2020/07/02 追記

度重なるブログ移転・ブログシステムのアップデートにより崩れた記事を校正。ついでに Ruby で書き直してみました。

#!/usr/bin/ruby

def pknot2cknot(str)
  ar = []
  c = 1
  x = nil
  str.split(' ').each_cons(2) do |n, m|
    if n != m
      ar << [n, c]
      c = 1
    else
      x = n
      c += 1
    end
  end
  (ar << [x, c]).join(' ')
end

def cknot2pknot(str)
  Hash[*str.split(' ')].map{|n, m|"#{n} "*m.to_i}.join.strip
end

p pknot2cknot("0 0 0 0 1 2 3 3 3 3") # => "0 4 1 1 2 1 3 4"
p cknot2pknot("0 4 1 1 2 1 3 4")     # => "0 0 0 0 1 2 3 3 3 3"

劇的に小さくなると思いきや、そんなことなかったのが残念。

Array#partition でも Array#group_by でもなく、配列から隣合う要素を比較し、区切りを見つけて多次元配列を返すメソッド…ないかなぁ。

OpenCASCADE 6.4.0 を Microsoft Visual Studio 2008 (vc9) 32bit 環境でビルドする

OpenCASCADE の最新版 6.4.0 からは、それまでの 6.3.* が VisualStudio 2003 ベースで開発されていたのに対し、 2005 ベースで開発されている。

続きを読む OpenCASCADE 6.4.0 を Microsoft Visual Studio 2008 (vc9) 32bit 環境でビルドする