IFCのデータの紐づけ構造を理解したい
IFCを可視化したけれども、形状情報とかいろんなデータがどこにどういう形式になっているのかわからないので、それを調べる。
前のスクラップ
たとえばスラブ。エンティティのつながりを可視化してみても、形状情報どこなの?状態
このスラブをBIMvisionで見るとここ
ドキュメント見て調べるのが確実かな
IfcSlab
のドキュメントを見る
Attributes
を確認すると以下の継承になっている
- IfcRoot (4)
- IfcObjectDefinition (7)
- IfcObject (5)
- IfcProduct (5)
- IfcElement (13)
- IfcSlab (1)
IfcSlab
固有の情報は PredefinedType
(床とか屋根とかの情報)の一つだけ。
つまり形状情報などのそれ以外の情報はIfcElement
より上の親のエンティティで定義されている。
IfcElement
のドキュメントを見る
うーん、ドキュメント見てもよくわからない。
わからないので実際のIFCを見てみる。
この場合、IfcElement
のAttributesは、ContainedInStructure
とProvidesBoundaries
の2つだけ。
ContainedInStructure
はたどるとIfcBuildingStorey
があるから、これはどこの階層?にあるオブジェクトであるかの情報。
ProvidesBoundaries
はたどるとIfcRelSpaceBoundary
がある。これは区切り?らしい。よくわからないのでBIMvisionで確認してみると以下のように。
この場合だとスラブに隣接しているIfcSpace
(この場合部屋)の情報みたい。
つまりIfcElement
では、直接的な位置や形状情報は持っていなさそう。
(ただ、壁とかで穴が開いているとかだと関わってきそうな気もするけどひとまずそれは後で考える。)
次は IfcProduct
のドキュメントを見る。多分ここなんだろうなーと思ってる。
ブラウザの翻訳
IfcProductは、幾何学的または空間的なコンテキストに関連するオブジェクトの抽象表現です。
形状と位置情報は、IfcProduct
で定義していそう。
次は、ObjectPlacement
とRepresentation
を確認していく。
沼の気配がするから形状はやめよう。
バウンディングボックスを取れることがわかったので良しということで。
Property どこから取得してるか確認してた。IsDefinedBy
から取得できるデータみたい。
BIMvisionで見れる情報と同じっぽい。
IfcOpenShell
で取得している情報とも同じっぽい。
slab = model.by_type("IfcSlab")[0]
psets = ifcopenshell.util.element.get_psets(slab)
# {'Pset_SlabCommon': {'ThermalTransmittance': 0.4, 'id': 34536},
# 'AC_Pset_Name': {'Name': 'Bodenplatte', 'id': 34544},
# 'ArchiCADProperties': {'Komplette Element-ID': 'Bodenplatte',
# 'Kompakte Element-ID': 'Bodenplatte',
# 'Name des Sachmerkmal-Objekts': '',
# 'Ursprungsgeschoss': 'Erdgeschoss',
# 'Ebene': 'Decken',
# 'Typ': 'Decke',
# 'Geschützt': False,
# 'Baustoff / Mehrschichtiger Aufbau / Profil / Schraffur': 'Stahlbeton 65690',
# 'Etikettentext': '',
# 'Eindeutige ID': 'E4D9CD4B-CA43-4735-94BD-1FD4376BD455',
# 'Verknüpfte Änderungen': '',
# 'Tragende Funktion': 'Nicht definiert',
# 'Lage': 'Nicht definiert',
# 'Element-Klassifizierung': 'Decke',
# 'Umbau-Status': 'Bestand',
# 'Anzeigen auf Umbau-Filter': 'Alle relevanten Filter',
# 'Struktur - Typ': 'Einfach',
# 'ARCHICAD IFC ID': '1pPHnf7cXCpPsNEnQf8_6B',
# 'Externe IFC ID': '',
# 'Oberfläche oben': 'Beton',
# 'Oberfläche Kante': 'Beton',
# 'Oberfläche unten': 'Beton',
# 'Baustoff/Mehrschichtiges Bauteil': 'Stahlbeton 65690',
# ...
# 'Brutto-Oberflächenbereich der Deckenkanten mit Löchern': 8.8,
# 'Brutto-Volumen de Decke mit Löchern': 24.0,
# 'Höhenwert Oberkante': 0.0,
# 'Höhenwert Unterkante': -0.2,
# 'id': 34643}}
このプロパティの中にIfcElementQuantity
があってこれに面積とかの情報とかもあるけど、形状情報とは別にラベル情報的に記録されているっぽい?
おそらくネイティブBIMソフトだと自動計算されてこのあたりのプロパティが自動設定されるんだと思う。IFCだとデータ構造的に形状情報と同期しているわけではなさそう。
ためしにIsDefinedBy
に接続されている「#34539, #34547, #34602, #34613, #34645」をIFCファイルから削除して表示したら、読み込み時にエラーもなく、プロパティ欄からなくなった。
つまりプロパティ欄に面積や長さとかの情報があったとしても、これらはモデルの形状情報として使用しているわけではなく、完全に付加情報として使用している。
(プロパティ欄がなくても面積などの計算がBIMvisionでできていることも確認)
IfcOpenShellのドキュメント見つつ
get_decomposition
は指定の要素の下位に属するIfcElement
をすべて取得しているんだろうと思って、グラフも一緒に見てたんだけど、取得結果とグラフとで数が合わないなんで!?と思ってソースコード見たら重複許してた。ユニークしたら同じになった。
import ifcopenshell.util.element
for storey in model.by_type("IfcBuildingStorey"):
elements = ifcopenshell.util.element.get_decomposition(storey)
print(f"There are {len(elements)} located on storey {storey.Name}, they are:")
for element in elements:
print(element.Name)
get_decomposition
の実装
def get_decomposition(element, is_recursive=True):
"""
Retrieves all subelements of an element based on the spatial decomposition
hierarchy. This includes all subspaces and elements contained in subspaces,
parts of an aggreate, all openings, and all fills of any openings.
:param element: The IFC element
:type element: ifcopenshell.entity_instance.entity_instance
:return: The decomposition of the element
:rtype: list[ifcopenshell.entity_instance.entity_instance]
Example:
.. code:: python
element = file.by_type("IfcProject")[0]
decomposition = ifcopenshell.util.element.get_decomposition(element)
"""
queue = [element]
results = []
while queue:
element = queue.pop()
for rel in getattr(element, "ContainsElements", []):
queue.extend(rel.RelatedElements)
results.extend(rel.RelatedElements)
for rel in getattr(element, "IsDecomposedBy", []):
queue.extend(rel.RelatedObjects)
results.extend(rel.RelatedObjects)
for rel in getattr(element, "HasOpenings", []):
queue.append(rel.RelatedOpeningElement)
results.append(rel.RelatedOpeningElement)
for rel in getattr(element, "HasFillings", []):
queue.append(rel.RelatedBuildingElement)
results.append(rel.RelatedBuildingElement)
for rel in getattr(element, "IsNestedBy", []):
queue.extend(rel.RelatedObjects)
results.extend(rel.RelatedObjects)
if not is_recursive:
break
return results
位置情報のIfcLocalPlacement
理解した。
PlacementRelTo
が相対位置参照元でRelativePlacement
それを元にした参照位置になるみたい。
この例だとIfcSite
の位置を基準にしてIfcBuilding
の相対位置が設定されている。同様にIfcBuildingStorey
はIfcBuilding
を、IfcWallStandardCase
はIfcBuildingStorey
を基準にしてRelativePlacement
の属性で相対位置が設定されている。
最初なんで位置情報がこんなに連なってるんだろうと思ってたけど、相対位置関係を定義するためだったのね。
形状情報は、IfcProduct.Representation
> IfcProductDefinitionShape.Representations
> IfcShapeRepresentation.Items
で定義されている。形状はそこから個別に定義していて、同じ形状でも異なった表現方法もできるっぽい。
そして同じ部材はIfcMappedItem
を使用して使いまわしている。(使い回しがない場合は青の部分はなくて直接赤部分につながる。)
BIMvisionで表示させると以下の部分。位置や回転情報だけが異なるので、Representation
については同じものを参照するようにして、ObjectPlacement
で位置情報を変えているよう。
web-ifc
だと形状定義は、IfcGeometryProcessor.cpp の GetMesh
で再帰的に処理している。そして特定のエンティティであればジオメトリ情報を取得して返している。すべてのジオメトリ情報を取得しているわけではない。
たとえば以下のような場合、IfcExtrudedAreaSolid
はソースコードに記載があるので形状取得できるが、IfcBoundingBox
は形状取得するようになっていないので取得されない。
以下で試しに形状を取得しようとすると、IfcExtrudedAreaSolid
の形状を取得して、IfcBoundingBox
についてはエラーメッセージが出力される。IfcPolyline
はソースコード上で ignore polylines as meshes
となっているので形状取得されない。
const fs = require("fs");
const WebIFC = require('web-ifc');
async function OpenIfc(filename) {
const ifcData = fs.readFileSync(filename);
await ifcapi.Init();
return ifcapi.OpenModel(ifcData);
}
async function LoadFile(filename) {
const modelID = await OpenIfc(filename);
return modelID;
}
const ifcapi = new WebIFC.IfcAPI();
ifcapi.SetWasmPath("");
LoadFile('AC20-FZK-Haus.ifc').then(function(modelID) {
// IfcWallStandardCase
const expressID = 15042
ifcapi.GetFlatMesh(modelID, expressID)
});
IfcOpenShell
では IfcShapeRepresentation
が RepresentationIdentifier = Body
のデータについて、形状取得している(っぽい)。IFCファイルを直接編集して検証した。
- 元データ
#15016= IFCSHAPEREPRESENTATION(#118,'Body','SweptSolid',(#15006));
#15024= IFCSHAPEREPRESENTATION(#375,'Box','BoundingBox',(#15023));
#15037= IFCPRODUCTDEFINITIONSHAPE($,$,(#15016,#15024,#15033));
- #15037から#15016を削除するとエラーになる(Bodyのデータが参照できなくなるので)
#15016= IFCSHAPEREPRESENTATION(#118,'Body','SweptSolid',(#15006));
#15024= IFCSHAPEREPRESENTATION(#375,'Box','BoundingBox',(#15023));
#15037= IFCPRODUCTDEFINITIONSHAPE($,$,(#15024,#15033));
- そこから #15024の 'Box' → 'Body' に変更すると正常
#15016= IFCSHAPEREPRESENTATION(#118,'Body','SweptSolid',(#15006));
#15024= IFCSHAPEREPRESENTATION(#375,'Body','BoundingBox',(#15023));
#15037= IFCPRODUCTDEFINITIONSHAPE($,$,(#15024,#15033));
- #15016を 'Body' → 'Box' に変更するとエラー(Bodyのデータがなくなるので)
#15016= IFCSHAPEREPRESENTATION(#118,'Box','SweptSolid',(#15006));
#15024= IFCSHAPEREPRESENTATION(#375,'Box','BoundingBox',(#15023));
#15037= IFCPRODUCTDEFINITIONSHAPE($,$,(#15016,#15024,#15033));
検証に使用したコード
import ifcopenshell
import ifcopenshell.geom
model = ifcopenshell.open(path)
element = model.by_type('IfcWall')[0]
settings = ifcopenshell.geom.settings()
shape = ifcopenshell.geom.create_shape(settings, element)
でも RepresentationIdentifier = Body
だからといって何でも形状取得するわけではない。
以下はIfcAnnotation
の形状を取得する検証。
(ただこの検証はIFCのデータとしては破損した状態にしているだと思う。)
element = model.by_type('IfcAnnotation')[0]
settings = ifcopenshell.geom.settings()
shape = ifcopenshell.geom.create_shape(settings, element)
IFCファイルを直接編集しつつ、上記のコードを実行した結果が以下。
- 元(取得できない)
#15366= IFCSHAPEREPRESENTATION(#15265,'Annotation','Annotation2D',(#15272,#15286,#15302,#15313,#15328,#15339,#15350,#15361));
- 'Body'にしても取得できない
#15366= IFCSHAPEREPRESENTATION(#15265,'Body','Annotation2D',(#15272,#15286,#15302,#15313,#15328,#15339,#15350,#15361));
- テキストも混ざっているので、それらを削除して一つにしても無理( #15286 は `IfcGeometricCurveSet` )
#15366= IFCSHAPEREPRESENTATION(#15265,'Body','Annotation2D',(#15286));
- 形状を壁のものにすると取得できる
#15366= IFCSHAPEREPRESENTATION(#15265,'Body','Annotation2D',(#15006));
つまりは IfcOpenShell
では、
-
IfcShapeRepresentation.RepresentationIdentifier = Body
であればジオメトリの取得を試み、 -
IfcShapeRepresentation.Items
のエンティティがジオメトリ取得定義されていれば形状取得がされる
ちゃんと調べてないけどIfcOpenShell
ではテキストやアノテーションのような2D情報は取得する定義が書かれていないのではないかと推測。
なんか紐づけではなくて形状の話になってるのでこっちのスクラップに移る
紐づけ構造は確認しきれているわけではないと思うのでまだクローズにはしないでおく。