Open15

IFCのデータの紐づけ構造を理解したい

kiyukakiyuka

IfcSlabのドキュメントを見る

https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcSlab.htm

Attributes を確認すると以下の継承になっている

  • IfcRoot (4)
  • IfcObjectDefinition (7)
  • IfcObject (5)
  • IfcProduct (5)
  • IfcElement (13)
  • IfcSlab (1)

IfcSlab固有の情報は PredefinedType(床とか屋根とかの情報)の一つだけ。
つまり形状情報などのそれ以外の情報はIfcElementより上の親のエンティティで定義されている。

kiyukakiyuka

IfcElementのドキュメントを見る

https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcElement.htm

うーん、ドキュメント見てもよくわからない。
わからないので実際のIFCを見てみる。

この場合、IfcElement のAttributesは、ContainedInStructureProvidesBoundariesの2つだけ。
ContainedInStructureはたどるとIfcBuildingStoreyがあるから、これはどこの階層?にあるオブジェクトであるかの情報。
ProvidesBoundariesはたどるとIfcRelSpaceBoundaryがある。これは区切り?らしい。よくわからないのでBIMvisionで確認してみると以下のように。

この場合だとスラブに隣接しているIfcSpace(この場合部屋)の情報みたい。

つまりIfcElementでは、直接的な位置や形状情報は持っていなさそう。
(ただ、壁とかで穴が開いているとかだと関わってきそうな気もするけどひとまずそれは後で考える。)

kiyukakiyuka

沼の気配がするから形状はやめよう。

バウンディングボックスを取れることがわかったので良しということで。

kiyukakiyuka

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だとデータ構造的に形状情報と同期しているわけではなさそう。

kiyukakiyuka

ためしにIsDefinedByに接続されている「#34539, #34547, #34602, #34613, #34645」をIFCファイルから削除して表示したら、読み込み時にエラーもなく、プロパティ欄からなくなった。

つまりプロパティ欄に面積や長さとかの情報があったとしても、これらはモデルの形状情報として使用しているわけではなく、完全に付加情報として使用している。
(プロパティ欄がなくても面積などの計算がBIMvisionでできていることも確認)

kiyukakiyuka

IfcOpenShellのドキュメント見つつ

https://blenderbim.org/docs-python/ifcopenshell-python/code_examples.html

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の実装

element.py
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
kiyukakiyuka

位置情報のIfcLocalPlacement理解した。
PlacementRelToが相対位置参照元でRelativePlacementそれを元にした参照位置になるみたい。

https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcLocalPlacement.htm

この例だとIfcSiteの位置を基準にしてIfcBuildingの相対位置が設定されている。同様にIfcBuildingStoreyIfcBuildingを、IfcWallStandardCaseIfcBuildingStoreyを基準にしてRelativePlacementの属性で相対位置が設定されている。

最初なんで位置情報がこんなに連なってるんだろうと思ってたけど、相対位置関係を定義するためだったのね。

kiyukakiyuka

形状情報は、IfcProduct.Representation > IfcProductDefinitionShape.Representations > IfcShapeRepresentation.Items で定義されている。形状はそこから個別に定義していて、同じ形状でも異なった表現方法もできるっぽい。

そして同じ部材はIfcMappedItemを使用して使いまわしている。(使い回しがない場合は青の部分はなくて直接赤部分につながる。)

BIMvisionで表示させると以下の部分。位置や回転情報だけが異なるので、Representationについては同じものを参照するようにして、ObjectPlacementで位置情報を変えているよう。

kiyukakiyuka

web-ifcだと形状定義は、IfcGeometryProcessor.cppGetMesh で再帰的に処理している。そして特定のエンティティであればジオメトリ情報を取得して返している。すべてのジオメトリ情報を取得しているわけではない。

たとえば以下のような場合、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)
});
kiyukakiyuka

IfcOpenShellでは IfcShapeRepresentationRepresentationIdentifier = 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)

https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcShapeRepresentation.htm

kiyukakiyuka

でも 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情報は取得する定義が書かれていないのではないかと推測。