Rhino・Grasshopper:ブール演算の差(BooleanDifference・SolidDifference)の演算時間と代替案

ブール演算は、ソリッド(あるいはサーフェス)同士を直接足し引きするため、直観的にわかりやすく便利ですが、一方で場合によっては失敗しやすかったり、処理が重かったりという側面もあります。

ここでは「板に多量の穴を作成する」という処理を例に、ブール演算も含め、どのような方法があり、どの方法が計算時間や作業時間の観点で効率よく処理が行えるか検証してみました。

検証モデル

下図のような、2枚の板(厚みのある閉じたポリサーフェス)と、その表面にそれぞれ穴作成用の曲線として30×30=900個の円(閉じた曲線)がある状況を考えます。
板は、片方はXY平面に平行、もう片方は30度傾斜があります。
穴は円を用いて板に対して垂直になるように作成するとします。

検証に使ったPCのスペックは、一般的なビジネスノートレベルで以下の通りです。

  • 14インチノートPC
  • CPU:Core i7-1165G7
  • メモリ:16GB

案1:【Rhinoで】ExtrudeCrv → BooleanDifference

まず下準備として、[ExtrudeCrv]コマンド(ソリッド=はい、両方向)で円を押し出します。
1回のコマンドでは、押し出し方向が1つしか指定できないので、今回のように片方の面に傾斜がある(押し出し方向が複数ある)場合は、2回に分けてコマンドを実行する必要があります。

(↑全部まとめて押し出すと上図のように傾斜側も鉛直方向に押し出されてしまいます)

<穴作成にかかる演算時間>

・2枚まとめての場合

[BooleanDifference]コマンド実行 → 2枚の板を選択しEnter → 曲線をすべて選択し、Enterしたタイミングから計測した結果、穴が作成されたオブジェクトが表示されるまで「約42秒」でした。

・1枚ずつの場合

ちなみに、2枚まとめてではなく、
[BooleanDifference]コマンド実行 → 1枚の板を選択しEnter → その板に関連する曲線だけ選択しEnterしたタイミングから計測した結果、オブジェクトが表示されるまで、傾斜の無い面で「約10秒」、傾斜のある面で「約20秒」でした。

再度コマンド実行し、手動で操作する時間などもあるので一概に比較はできませんが、失敗する可能性も含めて、オブジェクト別にある程度小分けにして実行した方が、安全的にも精神衛生的にも良いかもしれません。

案2:【Rhinoで】MakeHole

[MakeHole]コマンドを使った方法です。
閉じた曲線 → ポリサーフェス → 深さを指定することで穴が作成できます。
曲線を事前にソリッド化せず曲線のまま実行できるメリットがありますが、押し出し方向が複数ある場合には、コマンドを複数回に分けて実行する必要があります。

<穴作成にかかる演算時間>

[MakeHole]コマンド実行→対象の板上の閉じた曲線を選択しEnter → 1枚の板を選択しEnter → Enter(=深さは貫通)したタイミングから計測した結果、オブジェクトが表示されるまで、傾斜の無い面で「約11秒」、傾斜のある面で「約19秒」でした。

穴作成処理にかかる時間自体は[BooleanDifference]とほとんど同じでしたが、曲線のまま適用でき、ソリッドを事前に用意する必要がないというメリットがあります。

案3:【Grasshopperで】Extrude → SolidDifference

Grasshopperを使った方法です。アルゴリズム作成の手間がありますが、一度作ればパラメータ変更に伴う繰り返し作業が不要になります。

板2枚と穴曲線をそれぞれ[Brep]と[Curve]にSetします。

[Planar]で穴曲線の方向を取得し、その方向に[Extrude]で押し出します。
交差するように[Move]で位置を調整します。

(クリックで拡大表示します)

<穴作成にかかる演算時間>

・データ構造を考慮せず、リストをそのまま入力した場合

データ構造は特に考えず、[Solid Difference]のBreps A 端子に板2枚の[Brep]を、Breps B 端子に[Move]後のGeometryを繋いで演算を実行したところ演算終了まで「8.6分(516秒)」でした。

・板別にリストを分け、ツリー状のデータを入力した場合

最初のSet時に[Entwine]でリストを分けておくと、[Solid Difference]時に余分なデータの組合せが無くなり、少し計算は速くなります。
今回は「4.7分(282秒)」になりました。

データ構造を考慮してもGrasshopperでのブール演算は非常に時間がかかります。

(ここでは趣旨から外れるので割愛しますが、汎用性を考えるとリストを分けるのは[Entiwine]で直接分けるのではなく、円と板の距離や法線方向を求めてそれらを元にパスを振り分けるのが理想です)

・スクリプトを用いて順番にブール演算した場合

ちなみに[C# Script]を使用して、順番にRhinoCommonのCreateBooleanDifferenceを行った場合は「4.1m(246秒)」でした。

少し速くなりましたが、基本的に理屈は同じなので劇的な変化はないようです。

(クリックで拡大表示します)

案4:【Grasshopperで】Project + Loft → BoundarySurface → BrepJoin

ブール演算は使用せず、エッジ曲線から板の表面サーフェスと穴の側面サーフェスを分けて作成し、最後に合成する方法です。

円を法線方向に対して[Project]し、板の裏側の円を作成すれば[Loft]で穴の側面サーフェスが作成できます。

(クリックで拡大します)

[Deconstruct Brep]で板のエッジを取得して、投影した円と合わせて[Boundary Surface]を適用すれば同じ平面内にある平面曲線を判断してサーフェスを作成できます。
これが表面サーフェスとなります。

(クリックで拡大します)

最後に穴側面サーフェスと表面サーフェスを[Brep Join]で合成すれば、見た目上はブール演算したものと同じ結果が得られます。

<穴作成にかかる演算時間>

この方法では穴作成までにいくつかの工程があるので、Profilerで表示されている時間を足してみます(秒数が表示されていないコンポーネントはほとんど時間がかかっていないということなので除外)。

0.748秒(Project)+0.133秒(Loft)+19.5秒(BoundaySurface)+0.708秒(BrepJoin)=「約21秒

この方法だとブール演算を行った場合の10分の1以下の時間で完了しています。

面を張り直すことができないような特殊な状況でない限りは、この方法が計算時間、繰り返し作業時間の両方の観点からは効率的と言えそうです。
ただし、データ構造の理解などアルゴリズム作成の難易度は少し高くなります。

・シングルスレッド処理の場合

また、[Project][Boundary Surface]はデフォルトでPararrel computingオプションがオンになっており、複数のコアを用いた並列計算が行われています。
このオプションを外し、シングルスレッドで演算を行った場合は、
Projectは「2.7秒」、BoundaySurfaceは30.1秒となり、合計で「約34秒」となりました。
並列化の効果が出ていることがわかります。

まとめ

今回の検証結果をまとめると以下の通りです。

ツール手順穴作成にかかった演算時間
RhinoExtrudeCrv→BooleanDifference(まとめて)42秒
RhinoExtrudeCrv→BooleanDifference(分けて)10秒+20秒
RhinoMakeHole11秒+19秒
GHExtrude→SolidDifference(リストそのまま)516秒
GHExtrude→SolidDifference(リスト分割)282秒
GHExtrude→SolidDifference(スクリプト)246秒
GHProject+Loft→BoundarySurface→BrepJoin(並列処理)21秒
GHProject+Loft→BoundarySurface→BrepJoin(シングル)34秒

Rhinoでのコマンド操作の場合はオブジェクトを選択したりコマンドを実行する時間も掛かりますし、Grasshopperの場合はアルゴリズム作成やBakeして表示メッシュを生成する時間なども掛かるため、それぞれの方法は一概には比較できません。
演算時間はあくまで参考程度にお考えいただければと思います。

上記で挙げた方法はあくまで一例ですので、対象のモデルや目的に応じて最適な方法はほかにもあると思います。

これらのデータは以下からダウンロードできるようにしておきますので、ほかにもこんな方法があるよ、こっちの方が速いんじゃない?、というものがあれば是非お問い合わせフォームやSNSなどで教えてください!

Benchmark_Boolean.gh