Objectives

IFC(Industry Foundation Classes) 포맷의 구조와 특성을 분석하여 건설 정보 모델링에서의 활용 가능성을 평가하는 것이다. 구체적으로는:

  1. IFC 포맷의 데이터 구조와 저장 체계를 이해하고, 특히 건축 요소들 간의 관계성 표현 방식을 파악한다.
  2. ifcopenshell-python과 blender-bim 등 주요 IFC 처리 도구들의 특성과 활용 방법을 분석한다.
  3. 기존 랜드북 렌더링 작업 프로세스와 비교하여 IFC 기반 워크플로우의 실무 적용 가능성과 한계점을 검토한다.

이를 통해 IFC 포맷이 제공하는 데이터 관리 및 상호운용성 측면의 이점을 파악하고, 향후 건설 정보 모델링 시스템 개발에 활용할 수 있는 인사이트를 도출하고자 한다.


What is IFC?

IFC (Industry Foundation Classes)는 건축, 건물 및 건설 산업 데이터를 설명하기 위한 CAD 데이터 교환 데이터 스키마이다. 이는 단일 공급업체 또는 공급업체 그룹에 의해 제어되지 않는 플랫폼 중립적인 개방형 데이터 스키마 사양이다. 소프트 웨어간 정보 공유를 위해서 만들어졌으며 국제표준기반으로 buildingSMART에 의해 개발되고 있다.


How to Read IFC


Loading and Manipulating IFC Files in Blender

  • 예시 파일
    • https://github.com/myoualid/ifc-101-course/tree/main/episode-01/Resources
예시 파일


IFC 형식의 구조와 관계들

  • IFC의 spatial structure
    1. Project
      • aggregates
    2. Site
      • aggregates
    3. Facility: building( bridge, road, railway)
      • aggregates
    4. Building storey ( 층 )
      • Contains
    5. Products (building elements)
IFC의 spatial structure - 예시 파일
IFC의 spatial structure 다이어그램램



    file = ifcopenshell.open(r"C:\Users\MAD_SCIENTIST_21.ifc")
    project = file.by_type("IfcProject")[0]
    site = project.IsDecomposedBy[0].RelatedObjects[0]
    building = site.IsDecomposedBy[0].RelatedObjects[0]
    building_storeys = building.IsDecomposedBy[0].RelatedObjects

    for building_storey in building_storeys:
        print(building_storey)
        
    sous_sol = building_storeys[0]
    sous_sol.get_info()
    rel_contained_structure = sous_sol.ContainsElements[0]
    rel_contained_structure.RelatedElements

    for element in rel_contained_structure.RelatedElements:
        print(element)

bleder python script로 다음 코드를 이용하여 출력한 요소들은 blender에서 우측 패널에 보이는 object들과 동일하다. (IFC의 spatial structure - 예시 파일 그림의 패널)

출력 결과


IFC Class Inheritance Structure and Properties

크게 rooted class, non-rooted class로 나뉜다.

rooted class

  • ifcroot라는 class로부터 상속
  • cardinality: 필수인지 아닌지
  • 나머지는 optional인데 track item 잘 하기 위한 용도
  • 3가지 subclass
    • ifcObjectDefinition
    • ifcPropertyDefinition
    • ifcRelationship
  • attribute inheritance 살펴펴보면, 어떤 부모로부터 어떤 attribute를 상속한 것인지 볼 수 있다.
rooted class


code

object 좌클릭, → object property 누르면 ifc 속성들을 보고 수정할 수 있다.

ifc properties


ifc construction type 은 relation인데 클릭하면 같은 타입을 다 볼 수 있다.

ifc construction type


Attributes and Property Sets

property set이 매우 중요한데 schema에 주어진 template 대로 이 부분이 없으면, 지정한 custom으로 add 된다. inherited pset이 있는데, wall type으로부터 온다(quantity set은 수치에 대한 속성).

attribute와 property set


code

위의 내용들을 ifcopenshell 코드로 접근해보자.


    rooted_entities = file.by_type("IfcRoot")

    ifc_building_element_entities = set()
    for entity in rooted_entities:
        if entity.is_a("IfcBuildingElement"):
            ifc_building_element_entities.add(entity.is_a())

    my_wall = file.by_id("1K9fMEc5bCUxo4LlWWYA9b")
    my_wall.Description
    my_wall.OwnerHistory
    my_wall.Name
    my_wall.GlobalId
    my_wall.Description
    my_wall.Tag
    my_wall.PredefinedType
    my_wall.IsTypedBy[0].RelatingType
    # similar construction type 보는 것과 유사한 기능.
    my_wall.IsTypedBy[0].RelatedObjects

    my_wall.is_a()

    my_wall.is_a("IfcRoot")

    my_wall.is_a("IfcBuildingElement")

    my_wall.is_a("IfcProduct")

    my_wall.is_a("IfcWall")


Property Sets and Quantity Sets

위에서 attribute들을 다뤘는데, 이걸로는 정보가 충분히 표현되지 않는다. IFC에서는 property set(Pset)과 quantity set(Qset)을 통해 더 많은 정보를 표현할 수 있다.

Property Set (Pset)

  • 객체의 추가적인 특성을 정의하는 속성들의 집합
  • 표준 Pset: buildingSMART에서 정의한 표준 속성 집합 (예: Pset_WallCommon)
  • Custom Pset: 사용자가 필요에 따라 정의할 수 있는 속성 집합
  • 주요 속성 타입:
    • Single Value: 문자열, 숫자, 불리언 등의 단일 값
    • Enumerated Value: 미리 정의된 값 목록에서 선택
    • Bounded Value: 상한값과 하한값이 있는 수치
    • List Value: 여러 값의 목록
    • Table Value: 2차원 데이터 구조

IfcWall의 isDefinedBy member에 있는 여러 객체 중 IfcRelDefinedsByProperties라는 객체가 있는데, 그 객체의 RelatingPropertyDefinition member가, IfcPropertySet을 인 것들을 대상으로 loop를 돈다. 그 pset의 HasProperties안에는 여러 IfcPropertySingleValue들이 있고, Name과 NominalValue등을 가지고 있다. 이걸 props에 dict로 저장

→ props들은 psets에 저장.


    from blenderbim.bim.ifc import IfcStore
    file = IfcStore.file
    path = IfcStore.path
    my_wall.IsDefinedBy


    my_wall.IsDefinedBy[0].RelatingPropertyDefinitio
    my_wall.IsDefinedBy[1].RelatingPropertyDefinitio
    my_wall.IsDefinedBy[2].RelatingPropertyDefinitio
    my_wall.IsDefinedBy[3].RelatingPropertyDefinitio
    pset = my_wall.IsDefinedBy[3].RelatingPropertyDefinition
    pset.HasProperties[0].Name
    pset.HasProperties[0].NominalValue.wrappedValue


    psets = {}

    if my_wall.IsDefinedBy:
        for relationship in my_wall.IsDefinedBy:
            if relationship.is_a("IfcRelDefinesByProperties") and relationship.RelatingPropertyDefinition.is_a("IfcPropertySet"):
                pset = relationship.RelatingPropertyDefinition
                props = {}
                for property in pset.HasProperties:
                    if property.is_a("IfcPropertySingleValue"):
                        props[property.Name] = property.NominalValue.wrappedValue
                psets[pset.Name] = props
                print(pset.Name + " was added!")

    psets

이걸 다 직접 해야하나? → util에 있음. 더 정교한 기능으로 구현되어 있다.


    import ifcopenshell.util.element
    ifcopenshell.util.element.get_psets(my_wall, psets_only=True)


Quantity Set (Qset)

  • 객체의 물리적 수량 정보를 포함하는 집합
  • 주요 수량 타입:
    • Length (길이)
    • Area (면적)
    • Volume (부피)
    • Weight (무게)
    • Count (개수)
    • Time (시간)

    my_wall.IsDefinedBy[0].RelatingPropertyDefinition.is_a("IfcQuantitySet")
    my_wall.IsDefinedBy[0].RelatingPropertyDefinition.Quantities

    my_wall.IsDefinedBy[0].RelatingPropertyDefinition.Quantities[0].Name
    my_wall.IsDefinedBy[0].RelatingPropertyDefinition.Quantities[0].LengthValue

    my_wall.IsDefinedBy[0].RelatingPropertyDefinition.Quantities[3].AreaValue
    my_wall.IsDefinedBy[0].RelatingPropertyDefinition.Quantities[3][3]

    qsets = {}

    if my_wall.IsDefinedBy:
        for relationship in my_wall.IsDefinedBy:
            if relationship.is_a("IfcRelDefinesByProperties") and relationship.RelatingPropertyDefinition.is_a("IfcQuantitySet"):
                qset = relationship.RelatingPropertyDefinition
                quantities = {}
                for quantity in qset.Quantities:
                    if quantity.is_a("IfcPhysicalSimpleQuantity"):
                        quantities[quantity.Name] = quantity[3]
                qsets[qset.Name] = quantities
                print(qset.Name + " was added!")

    qsets


수치단위 확인. 어떤 수치가 어떤 단위인지 명시되어 있다. 이 quantity들을 묶어서 export csv 등이 가능하다.


    project = file.by_type("IfcProject")
    project = project[0]
    for unit in project.UnitsInContext.Units:
        print(unit)


BIM application with python

streamlit을 활용한 web app front-end proto 개발 가이드. viewer 와 download csv, 통계 수치 등을 시각화 해준다.

ifc viewer


Prototyping


    import uuid
    import time
    import ifcopenshell
    import ifcopenshell.guid
    import json


    from ifc_builder import IfcBuilder

    O = 0.0, 0.0, 0.0
    X = 1.0, 0.0, 0.0
    Y = 0.0, 1.0, 0.0
    Z = 0.0, 0.0, 1.0


    create_guid = lambda: ifcopenshell.guid.compress(uuid.uuid1().hex)


    # IFC template creation

    filename = "hello_wall.ifc"
    # https://standards.buildingsmart.org/IFC/DEV/IFC4_3/RC2/HTML/schema/ifcdatetimeresource/lexical/ifctimestamp.htm
    # NOTE: 초단위.
    timestamp = int(time.time())
    timestring = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(timestamp))
    creator = "CKC"
    organization = "SWK"
    application, application_version = "IfcOpenShell", "0.7.0"
    project_globalid, project_name = create_guid(), "Hello Wall"

    # A template IFC file to quickly populate entity instances for an IfcProject with its dependencies
    template = (
        """ISO-10303-21;
    HEADER;
    FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
    FILE_NAME('%(filename)s','%(timestring)s',('%(creator)s'),('%(organization)s'),'%(application)s','%(application)s','');
    FILE_SCHEMA(('IFC4'));
    ENDSEC;
    DATA;
    #1=IFCPERSON($,$,'%(creator)s',$,$,$,$,$);
    #2=IFCORGANIZATION($,'%(organization)s',$,$,$);
    #3=IFCPERSONANDORGANIZATION(#1,#2,$);
    #4=IFCAPPLICATION(#2,'%(application_version)s','%(application)s','');
    #5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,%(timestamp)s);
    #6=IFCDIRECTION((1.,0.,0.));
    #7=IFCDIRECTION((0.,0.,1.));
    #8=IFCCARTESIANPOINT((0.,0.,0.));
    #9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
    #10=IFCDIRECTION((0.,1.,0.));
    #11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
    #12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
    #13=IFCSIUNIT(\*,.LENGTHUNIT.,$,.METRE.);
    #14=IFCSIUNIT(\*,.AREAUNIT.,$,.SQUARE_METRE.);
    #15=IFCSIUNIT(\*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
    #16=IFCSIUNIT(\*,.PLANEANGLEUNIT.,$,.RADIAN.);
    #17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
    #18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
    #19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
    #20=IFCPROJECT('%(project_globalid)s',#5,'%(project_name)s',$,$,$,$,(#11),#19);
    ENDSEC;
    END-ISO-10303-21;
    """
        % locals()
    )

    def run():
        print(type(template))
        # Write the template to a temporary file
        # temp_handle, temp_filename = tempfile.mkstemp(suffix=".ifc", text=True)
        # print(temp_filename)
        # with open(temp_filename, "w", encoding="utf-8") as f:
        #     f.write(template)
        # os.close(temp_handle)
        temp_filename = "temp.ifc"
        with open(temp_filename, "w", encoding="utf-8") as f:
            f.write(template)
        print(template)
        with open("result-bldg.json", "r", encoding="utf-8") as f:
            bldg_info = json.load(f)

        # Obtain references to instances defined in template
        ifc_file_template = ifcopenshell.open(temp_filename)

        # IFC hierarchy creation
        ifc_builder = IfcBuilder(bldg_info, ifc_file_template)

        # site 생성
        site_placement, site = ifc_builder.add_site("Site")
        # project -> site
        ifc_builder.add_rel_aggregates("Project Container", ifc_builder.project, [site])

        # building 생성
        building_placement, building = ifc_builder.add_building("Building", relative_to=site_placement)
        # site -> building
        ifc_builder.add_rel_aggregates("Site Container", site, [building])

        # storeys 생성
        storey_placement_list = []
        building_storey_list = []
        for i in range(bldg_info["unit_info"]["general"]["floor_count"]):
            storey_placement, building_storey = ifc_builder.add_storey(
                f"Storey_{i+1}", building_placement, elevation=i * 6.0
            )
            storey_placement_list.append(storey_placement)
            building_storey_list.append(building_storey)

        print(building_storey_list)
        # building -> storeys
        ifc_builder.add_rel_aggregates("Building Container", building, building_storey_list)

        ifc_walls = []

        # 외벽과 창문
        for i, (exterior_wall_polyline_each_floor, exterior_wall_area_each_floor, window_area_each_floor) in enumerate(
            zip(
                ifc_builder.ifc_preprocess.exterior_wall_polyline,
                ifc_builder.ifc_preprocess.exterior_wall_area,
                ifc_builder.ifc_preprocess.window_area,
            )
        ):

            for (
                exterior_wall_polyline_in_floor_polygon,
                exterior_wall_area_in_floor_polygon,
                window_area_in_floor_polygon,
            ) in zip(
                exterior_wall_polyline_each_floor,
                exterior_wall_area_each_floor,
                window_area_each_floor,
            ):
                for (exterioir_wall_polyline, exterior_wall_area, window_area_list) in zip(
                    exterior_wall_polyline_in_floor_polygon,
                    exterior_wall_area_in_floor_polygon,
                    window_area_in_floor_polygon,
                ):
                    print(storey_placement_list[i])
                    print(building_storey_list[i])

                    wall, windows = ifc_builder.add_exterior_wall(
                        storey_placement_list[i], exterioir_wall_polyline, exterior_wall_area, window_area_list
                    )

                    ifc_walls.append(wall)

                    ifc_builder.add_rel_contained_in_spatial_structure(
                        "Building Storey Container", building_storey_list[i], wall
                    )
                    print(windows)
                    for window in windows:
                        ifc_builder.add_rel_contained_in_spatial_structure(
                            "Building Storey Container", building_storey_list[i], window
                        )

        # 층 바닥
        for i, floor_slab_polyline_each_floor in enumerate(ifc_builder.ifc_preprocess.floor_slab_polyline):
            ifc_floor_slabs_in_a_floor = []
            for floor_slab_polyline_each_polygon in floor_slab_polyline_each_floor:
                floor_slab = ifc_builder.add_floor_slab(0.2, storey_placement_list[i], floor_slab_polyline_each_polygon)
                # , building_storey_list[i]
                print(floor_slab)
                ifc_floor_slabs_in_a_floor.append(floor_slab)
                ifc_builder.add_rel_contained_in_spatial_structure(
                    "Building Storey Container",
                    building_storey_list[i],
                    floor_slab,
                )

        # 세대 내벽
        ifc_interior_walls = []
        for i, (interior_wall_polyline_each_floor, interior_wall_area_each_floor) in enumerate(
            zip(
                ifc_builder.ifc_preprocess.interior_wall_polyline,
                ifc_builder.ifc_preprocess.interior_wall_area,
            )
        ):
            for (
                interior_wall_polyline,
                interior_wall_area,
            ) in zip(interior_wall_polyline_each_floor, interior_wall_area_each_floor):

                print(storey_placement_list[i])
                print(building_storey_list[i])

                wall = ifc_builder.add_interior_wall(
                    storey_placement_list[i],
                    interior_wall_polyline,
                    interior_wall_area,
                )

                ifc_interior_walls.append(wall)

                ifc_builder.add_rel_contained_in_spatial_structure(
                    "Building Storey Container", building_storey_list[i], wall
                )

        # material 적용 방식중 맞는 방식 확인 필요.
        # ifc_builder.add_material_style(ifc_interior_walls, ifc_builder.wall_style)
        # ifc_builder.add_material_style(ifc_walls, ifc_builder.glass_style)

        for i, flight_info_each_floor in enumerate(ifc_builder.ifc_preprocess.flight_info):
            ifc_stair = ifc_builder.add_stair(
                flight_info_each_floor,
                storey_placement_list[i],
            )
            ifc_builder.add_rel_contained_in_spatial_structure(
                "Building Storey Container", building_storey_list[i], ifc_stair
            )

        print(filename)

        # Write the contents of the file to disk
        ifc_builder.build()


    if __name__ == "__main__":
        run()


results


Conclusion

IFC 포맷은 예상했던 것보다 훨씬 더 다양한 요소들을 저장할 수 있도록 체계적인 구조를 갖추고 있다. 이러한 포괄적인 구조로 인해 문서화와 실제 사용법이 다소 복잡한 면이 있다. 특히 포함 관계나 연관 관계를 entity로 정의하는 구조적 특징은 매우 인상적이며, 향후 참고할만한 좋은 사례로 보임. ifcopenshell-python을 이용한 IFC 생성에 관한 인터넷 자료는 상대적으로 부족한 편이지만 대신 blender-bim이 주된 도구로 사용되고 있으며, 다른 프로그래밍 언어 바인딩이나 도구들에 대한 정보는 비교적 풍부합니다. 렌더링 작업 측면에서는 기존 방식과 비교했을 때 필요한 요소들을 직접 생성해야 하는 점에서 큰 차이가 없어 작업 효율성 향상을 기대하기는 어려워 보인다. 다만, 동일한 사전 처리 과정이 필요하더라도 이를 저장하고 제공하거나 관리할 수 있다는 점에서 장점이 있을 것으로 판단됨.


References