ACISトポロジー:一つの部品に、なぜBODY・LUMP・SHELLが必要なのか
立方体を例に ACIS、Parasolid、Open CASCADE の B-Rep データ構造を比較し、ACIS 側を見ていると、次のような名前の並びがよく出てきます。
BODY → LUMP → SHELL → FACE → LOOP → COEDGE → EDGE → VERTEX
面・エッジ・頂点は目の前のモデルと対応づけやすいものです。しかしその上にある BODY、LUMP、SHELL は、どれも「一つのもの」を表しているように見えます。なぜ三層に分ける必要があるのでしょうか。
まず二つの問いを考えてみましょう。ブロックの内部に完全に閉じた空洞をくり抜いたとき、それはまだ一つながりの材料でしょうか。空洞を貫通穴に変えたら、境界のつながり方はどう変わるでしょうか。
この二つのモデルは、どちらも BODY が一つ、LUMP が一つです。しかし通常の完全なソリッド表現では、前者の SHELL は二つ、後者は一つになります。この違いを理解するには、モデルオブジェクト、材料の連結性、境界の連結性をそれぞれ分けて見る必要があります。
本記事では BODY、LUMP、SHELL を整理し、あわせてオプションの SUBSHELL を紹介します。FACE 以下の向き・境界・隣接関係は、今後の記事で一層ずつ掘り下げます。
01 まず階層図を正しく読む
(図1:本シリーズのトポロジー・ロードマップ。SUBSHELL はオプションのグループ化、WIRE はワイヤ構造の分岐。共有参照、同階層のリンクリスト、一部の逆方向ポインタは省略。)
この図には注意点が二つあります。
一つ目は、よく見かける階層チェーンは「面を持つモデル」を表したものだという点です。ACIS はワイヤモデルも表現できるため、SHELL の下には WIRE という分岐があります。WIRE は COEDGE を介して EDGE を使い、FACE や LOOP を経由する必要はありません。
二つ目は、モデル内の参照関係が階層ツリーよりも豊かだという点です。たとえば隣接する面は同じ EDGE を使えますし、複数のエッジが同じ VERTEX につながることもあります。オブジェクトを階層に沿って展開すると、同じ下位オブジェクトに何度も出会うことがあります。後でエッジや頂点を数えるとき、この点が重要になります。
本記事では、大文字の BODY、LUMP などは ACIS のクラス名を指し、「ボディ・ランプ・シェル」といった表記は対応する概念を指します。ここでいう「ボディ」はモデルオブジェクトの一種であり、名前だけで必ず三次元の体積を持つと考えてはいけません。
02 BODY:一回のモデリング操作が対象とするモデルオブジェクト
ACIS において、BODY はこのトポロジー構造の最上位にあります。一つの BODY は複数の LUMP をまとめられるため、空間的に互いにつながっていない部分を含むことができます。
たとえばカット操作で長い部品を中央から完全に切断し、二つの部分が残ったとします。幾何的には二つはすでに分離していますが、カーネルのデータ構成上は同じ BODY の中に保持し続け、それぞれを一つの LUMP とすることができます。アプリケーション側で後からこの結果を二つの BODY に分けることも可能です。
つまり、BODY の数と連結部分の数は分けて考える必要があります。互いに接していない二つのブロックは、二つの BODY に分けて置くこともできれば、一つの BODY の中の二つの LUMP として置くこともできます。
BODY はソリッドだけでなく、シートやワイヤも表せる
(図2:次元の異なるモデルで BODY を理解する。シートの着色は表示用で厚みを表すものではない。混合モデルの各部分はこの例では互いに接していない。)
よくあるケースは次のとおりです。
| 種類 | 表す内容 | 直感的な例 |
|---|---|---|
| Solid body | 三次元のソリッド領域 | 体積を持つ機械部品 |
| Sheet body | 二次元のシート領域 | 曲面で表した理想的な薄板 |
| Wire body | FACE を持たないワイヤ構造 | 輪郭線、中心線、分岐したワイヤフレーム |
| Mixed body | 次元の異なる領域の組み合わせ | ソリッドに付加的なシートやワイヤが付いたモデル |
このほか、これらの領域を一切含まない空の BODY もあります。これは「BODY オブジェクトを取得できなかった」状態とは区別すべきです。空のオブジェクトと NULL ポインタは、表す状態が異なります。
開発者にとっては、BODY* を受け取った後も、モデルの種類と完全性を判断する必要があります。後続のアルゴリズムがどの入力を受け付けるかは、各インターフェースの要件次第です。
BODY は全体の変換も持つ
BODY には TRANSFORM を関連付けることができ、モデルのローカル座標とグローバル座標を結びつけます。
たとえばモデル全体を平行移動しても、配置位置が変わっただけで LUMP の数や各 LUMP の SHELL の数が変わるわけではありません。トポロジーが表す接続関係と、モデルが今どこに置かれているかは、別種の情報です。
実際のプログラミングでは、幾何評価が返す座標がどの空間のものかを区別する必要があります。BODY の変換を処理してくれるインターフェースもあれば、下位の幾何アクセスでは呼び出し側が自分で処理しなければならないものもあります。すべてのインターフェースに同じ前提を当てはめることはできません。
また、BODY は CAD アプリケーション層の「部品ノード」と同じものではありません。部品、アセンブリインスタンス、フィーチャー履歴などの概念は上位の製品側で組み立てられるものであり、それらをカーネルのオブジェクトにどう対応させるかはアプリケーションの設計次第です。
03 LUMP:モデルの中で互いにつながっている部分
LUMP は、BODY の中の一つの連結部分と考えることができます。通常のソリッドモデルでは、材料の中の経路で判断できます。ある点から出発し、常に材料の内部かその境界上にとどまったまま、別の点まで行けるかどうかです。
中実の直方体は一つの LUMP です。切断されて完全に分離した二つの直方体を同じ BODY に保持していれば、二つの LUMP に対応します。
この判断は密閉空洞も説明できます。内部に材料のない空間があっても、周囲の材料は一つながりのままです。空洞が材料を分断していない限り、LUMP の数は増えません。
(図3:四種類の三次元モデルの中央断面。いずれもモデルは完全かつ有限で、内部シートや接続ワイヤなどの非多様体構造は付加されていないものとする。青線は断面と交わる境界、オレンジ線は独立した内部空洞の境界を示す。)
図の D は誤読しやすいので注意が必要です。貫通穴の中央断面では材料が左右二つに分かれて描かれますが、三次元空間では穴の手前側と奥側でつながっています。したがって、二次元断面で二つの分離した領域が見えても、モデルに LUMP が二つあるとは直接結論づけられません。
連結に体積は必須ではない
LUMP はシートやワイヤにも適用されます。連結したシートモデルは一つの LUMP になり得ますし、連結したワイヤ構造の集まりも一つの LUMP になり得ます。混合モデルでは、次元の異なる領域どうしの接続も考慮する必要があります。
たとえば、二つのソリッドが一本の WIRE で結ばれ、その端点で有効な共有トポロジー接続が確立されていれば、この非多様体の混合表現では両者は同じ LUMP に属し得ます。WIRE に厚みはありませんが、それはモデルの点集合において接続を成立させる妨げにはなりません。
ここでいう「つながっている」とは、カーネル内の接続関係が幾何と整合していることを意味します。画面上でくっついて見えるだけでは、二つのオブジェクトが接続済みである証拠にはなりません。モデルは依然として独立した二組のエッジや頂点を保持しているかもしれません。
前回の記事で取り上げた非多様体モデルは、ここで具体的な居場所を得ます。LUMP の連結性は「ソリッドが厚みのある材料でつながっているかどうか」だけで理解してはいけません。
04 SHELL:一つの連結部分に、なぜ複数のシェルが必要なのか
通常のソリッドでは、SHELL は LUMP の境界のうち一つの連結部分を表します。
中実の直方体の六つの面は共有エッジでつながり、一組の完全な境界を形成します。これが一つの SHELL に対応します。直方体の内部に、外側の境界に触れない完全な密閉空洞をくり抜くと、境界は外表面と空洞の壁の二組になります。
外表面に沿って歩けば他の外表面には行けますが、モデルの境界をたどり続けて空洞の壁に行くことはできません。二組の境界の間には材料があるため、二つの SHELL に属することになります。
このときのモデル構成は次のとおりです。
BODY
└── LUMP:周囲の材料はつながったまま
├── SHELL:外側の境界
└── SHELL:密閉空洞の境界
最も混同しやすいのは、空洞内の何もない領域が二つ目の LUMP ではないという点です。この例では、LUMP は周囲の連結したソリッド領域を表し、内側シェルがその中の一つの空洞の境界を記述しています。
同じ材料の内部に互いに分離した密閉空洞がもう一つあれば、内側シェルがもう一つ必要になります。こうした通常の条件下では、一つの LUMP は一つの外側シェルと、いくつかの内側シェルに対応します。
貫通穴では、なぜ通常 SHELL が増えないのか
図3 の D を見てください。ブロックの外表面に沿って穴の入口まで行けば、入口の縁を越えて穴の壁に入り、穴の壁をたどって反対側の入口まで行けます。
外表面と穴の壁は一組の連結した境界を構成しているので、SHELL は一つのままです。「穴がある」というのは形状の特徴を述べているだけで、シェルの数を直接決めるものではありません。
| 比較モデル | 材料は連結しているか | 境界の連結部分の数 | LUMP / SHELL |
|---|---|---|---|
| 中実ブロック | はい | 1 | 1 / 1 |
| 分離した二つのブロック(一つの BODY を共有) | いいえ | 計 2 | 2 / 2 |
| 密閉空洞が一つ(材料は分断されていない) | はい | 2 | 1 / 2 |
| 貫通穴が一つ(材料は分断されていない) | はい | 1 | 1 / 1 |
この表の前提条件は図3 と同じです。空洞の壁と外表面の間に有効な接続 WIRE を追加すると境界構造が変わるため、表の数え方はそのまま当てはまりません。
内側シェルの法線はどちらを向くか
(図4:矢印はソリッド境界上の片側 FACE の法線を示す。外側シェルでも内側シェルでも、材料から非材料領域へ向かう。)
ソリッド領域を限定する片側 FACE の法線は、ソリッド材料から離れる側を向きます。
外側シェルでは法線は周囲の外部空間を向き、内側シェルでは空洞を向きます。したがって「法線は外向き」の「外」は材料を基準に理解する必要があり、一律に「部品の中心から離れる方向」と解釈してはいけません。
このことから、内側シェルと外側シェルの違いは空間的な大きさだけでは識別できないこともわかります。両者は同じ SHELL クラスを使っており、内外の意味はモデルの境界の向きと領域の関係を合わせて判断する必要があります。リンクリスト内の順序を、内側・外側シェルの分類根拠として扱うべきでもありません。
05 SHELL はシートやワイヤも含められる
ここまでの密閉モデルは直感をつかむのに便利でした。しかし ACIS の SHELL は、連結した FACE と WIRE からも構成でき、その範囲は「閉じたソリッドの外皮」よりも広いものです。
隣接する面は共有エッジでつながることもあれば、一つの頂点だけでつながることもあります。ワイヤと面は頂点を介して接続できます。もともと分離していた二つの面の集まりが有効に接続された WIRE で結ばれれば、それらは同じ SHELL になり得ます。
この種のモデルの接続点や接続エッジは、非多様体である場合があります。トポロジーを読むときは実際の共有関係をたどって判断する必要があり、見た目がいくつかの部品に見えるからといってグループ分けしてはいけません。
「開いている」と「不完全」は、面の意味と合わせて判断する
長方形の薄板は境界を持ち、正当な sheet body として存在できます。一方、中実の直方体の境界表現から面を一枚削除し、残りの面の「片側ソリッド境界」という意味を保ったままにすると、不完全なソリッド境界表現になります。
どちらも画面上では「開口のある面の集まり」に見えるかもしれませんが、材料としての意味は異なります。したがって、自由エッジや閉じていない外観を見ても、すぐに不正な sheet body だと断定することはできませんし、あらゆるソリッドモデリング操作を実行できるとみなすこともできません。
片側面・両側面と内包性の具体的な規則は、ここではまだ展開しません。次回 FACE を扱う際に、同じ幾何曲面がどのように異なる材料境界の意味を担うかを図で説明します。
06 データ構造はこれらのオブジェクトをどうつないでいるか
幾何的な例を理解した後でデータ構造を見ると、ポインタの用途がずっとわかりやすくなります。
BODY、LUMP、SHELL、SUBSHELL はいずれも ACIS の ENTITY から派生しています。これらは永続的なモデル管理に関わるもので、一回の関数呼び出しのために一時的に作られるグループではありません。
以下には本記事で扱う関連だけを挙げ、履歴、属性、モードなどのメンバーは省略します。ヘッダーファイルをもとに整理した関係の説明であり、完全なクラス定義ではありません。
| オブジェクト | 本記事で注目する関連 | 理解のしかた |
|---|---|---|
| BODY | LUMP リストの入口、全体の変換 | モデルオブジェクトから各連結部分へ入る |
| LUMP | 所属 BODY、次の LUMP、SHELL リストの入口 | 同じ BODY 内で各ランプを列挙し、それぞれのシェルへ入る |
| SHELL | 所属 LUMP、次の SHELL、直接の FACE/WIRE リスト、SUBSHELL リスト | 一組の連結した境界、または面・ワイヤ構造をまとめる |
| SUBSHELL | 親 SUBSHELL、兄弟 SUBSHELL、子 SUBSHELL、直接の FACE/WIRE リスト | シェル内部の再帰的なグループ化を表す |
(図5:同階層のリンクリストと下位への入口。LUMP B の下位構造と各リストの終端ポインタは省略。矢印は参照を表し、空間的な接続を表すものではない。)
ここでは二種類の「次」を区別する必要があります。
LUMP::next() は、同じ BODY 配下の LUMP を列挙するためのものです。二つの LUMP は遠く離れていても構わず、互いにつながっていなくても構いません。同様に、SHELL::next() は同じ LUMP 配下のシェルリストの関係であり、前のシェルから境界に沿って次のシェルへ行けることを意味しません。
ある FACE から共有エッジを越えて隣の FACE へ行くには、より下位のトポロジー接続を使う必要があります。それを表現するのは LOOP、COEDGE、EDGE などのオブジェクトです。
読み取り専用の走査は、まずこれらの入口から理解する
以下は列挙関係を説明するための概念的な疑似コードで、そのままコンパイルできる C++ の例ではありません。
lump = body.lump()
while lump が空でない:
shell = lump.shell()
while shell が空でない:
現在の shell を記録する
shell = shell.next()
lump = lump.next()
これで BODY 配下の LUMP と SHELL を数えることができます。走査中に構造を変更しなければ、リンクリストの列挙は理解しやすくなります。
さらに面を列挙する場合は、ACIS にある名前の似た二つのインターフェースに注意が必要です。
- SHELL::face_list() は、この SHELL に直接格納されている面のリストにアクセスします。
- 本記事で確認したヘッダーファイルでは、SHELL::face() と FACE::next() を組み合わせて、この SHELL 内の面を SUBSHELL 内のものも含めて完全に列挙します。
したがって、完全列挙インターフェースを使った後でさらに SUBSHELL を再帰してその面を追加すると、重複して数える恐れがあります。逆に直接リストだけを見ると、子グループ内の面を取りこぼす可能性があります。
列挙順序も SUBSHELL のグループ分けの変化に応じて変わることがあります。「三番目の面」をある設計フィーチャーの識別子として長期的に使うと、不安定な順序に依存することになります。
07 SUBSHELL:同じシェル内部のオプションのグループ化
一つの SHELL に面が大量にあると、面をまとめて繰り返し比較する必要のあるアルゴリズムがあります。SUBSHELL は面やワイヤを階層的なグループに整理し、バウンディングボックスを使って交差し得ないオブジェクトをまとめて先に除外できるようにします。
たとえば、二つの面グループの間で交差計算の候補を絞り込むとします。両グループのバウンディングボックスが、許容誤差を考慮した後も交わらなければ、この二グループ間の交差計算は除外できます。ボックスが交わる場合は、引き続き検査が必要だというだけで、曲面が交差していると断定はできません。

(図6:SHELL は FACE/WIRE を直接保持することも、SUBSHELL のグループを介して保持することもできる。SUBSHELL はさらに子グループを持てる。図は構成方法を説明するもので、特定のアルゴリズムが固定的に採用するグループ分け戦略を表すものではない。)
覚えておきたい規則がいくつかあります。
- SUBSHELL はオプションの構造で、単純なモデルではまったく存在しないこともあります。
- 同じ SHELL の異なる SUBSHELL 同士はつながっていても構いません。グループ分けは材料や境界が切断されていることを意味しません。
- SHELL 自身と各階層の SUBSHELL は、いずれも直接の FACE/WIRE リストを持てます。一つの面やワイヤは、そのうちのどこか一か所に格納されます。
- グループ分けはアルゴリズムの処理に応じて調整されることがあるため、安定した業務上の区分やフィーチャーの識別子には向きません。
本記事で確認した subshell.hxx では、SUBSHELL は parent、sibling、child といった階層アクセスを持っています。SHELL に直接属する SUBSHELL の parent() は NULL になり得ます。このインターフェースが返すのは親の SUBSHELL であって、任意の型の親オブジェクトではないからです。
バウンディングボックスはまだ計算されていないこともあれば、モデル変更後に取り直しが必要なこともあります。bound() のようなキャッシュアクセス用インターフェースを呼ぶときは、その約束事を読む必要があり、毎回自動的に使えるボックスが計算されると仮定してはいけません。
モデルの意味から見れば、内部にいくつ SUBSHELL が分けられていても、もとの連結した SHELL は同じ一つのシェルのままです。
08 一つのモデル上で対応づけてみる
一つの BODY の中に、互いに分離した二つの部品が入っているとします。A は密閉空洞を一つ持つブロック、B は普通の中実ブロックです。付加的なワイヤや内部シートはありません。
BODY
├── LUMP A
│ ├── SHELL A1:外側の境界
│ └── SHELL A2:密閉空洞の境界
└── LUMP B
└── SHELL B1:外側の境界
このとき、合計は BODY が 1、LUMP が 2、SHELL が 3 となります。




