Skip to content

2. Console with properties, quantities and type information

stefkeB edited this page Apr 1, 2021 · 1 revision

Getting started

We start very similar as with the minimal console application, but expand the output to also return the names and values of property sets and individual properties, including their value.

import sys
import ifcopenshell


# Our Print Entity function (recursive)
def print_entity(entity, level):
    print("{0}{1} [{2}]".format('.  ' * level, entity, entity.is_a()))

   # follow Containment relation
    if hasattr(ifc_object, 'ContainsElements'):
        for rel in ifc_object.ContainsElements:
            for child in rel.RelatedElements:
                print_entity(child, level + 1)

    # follow Aggregation/Decomposition Relation
    if hasattr(ifc_object, 'IsDecomposedBy'):
        for rel in ifc_object.IsDecomposedBy:
            for child in rel.RelatedObjects:
                print_entity(child, level + 1)

# Our Main function
def main():
    ifc_file = ifcopenshell.open(sys.argv[1])
    for item in ifc_file.by_type('IfcProject'):
        print_entity(item, 0)


if __name__ == "__main__":
    main()

Follow the IsDefinedBy attribute

First we add a small convenience function which allows us to print the indents more easily, as we'll need this in a few places.

def indent(level):
    spacer = '.  '
    for i in range(level):
        print(spacer, end='')

Properties, quantities but also type definitions can all be retrieved via IfcRelDefines relationships. Within the print_element method, we check if the attribute IsDefinedBy is available, as this indicates the availability of that relationship.

def print_entity(entity, level):
    indent(level), print('#' + str(entity.id()) + ' = ' + entity.is_a()
                         + ' "' + str(entity.Name) + '" (' + entity.GlobalId + ')')
    if hasattr(entity, 'IsDefinedBy'):

After that, we have to distinguish between the different specialisations of this relationship:

  • IfcRelDefinesByType is the class the relates an element (actually all classes derived from IfcObject) with a type. In that case, we retrieve the type and recursively call the print_entity method.
  • IfcRelDefinesByProperties is another variant, which is used to link an IfcPropertySetDefinition to an IfcObject. This definition is than retrieved via the .RelatingPropertyDefinition attribute.
    if hasattr(entity, 'IsDefinedBy'):
        for definition in entity.IsDefinedBy:
            if definition.is_a('IfcRelDefinesByType'):
                print_entity(definition.RelatingType, level + 1)

In the case of IfcRelDefinesByProperties we need to make another distinction, as this can either by an IfcPropertySet or a specialisation of this as an IfcElementQuantity (which is actually just a special version of a property set, but containing quantities instead of properties.

           if definition.is_a('IfcRelDefinesByProperties'):
                related_data = definition.RelatingPropertyDefinition
                # the individual properties/quantities
                if related_data.is_a('IfcPropertySet'):
                    print_element_properties(related_data, level + 1)
                elif related_data.is_a('IfcElementQuantity'):
                    print_element_quantities(related_data, level + 1)

You can notice that we will need a new method to print all properties and quantities. While we could expand our print_entity method, we want to keep it simple enough and a dedicated method seems easier to follow.

Method to display properties

The method to display properties will first print out the .Name attribute of the property, using the indent method to ensure it is nicely placed underneath the entity itself.

Then we follow the .HasProperties attribute which returns the list of all properties, which we can traverse using a for loop.

Finally, we display some information about this property:

  • The .Unit attribute contains a string to show the unit type. As it is often empty, it will display None. We should in that case go looking for the unit assignments at the IfcProject instance. We also test if the attribute is actually available.
  • The value of the property depends on the property class and is located in different attributes. We initiate this as a string '<not handled>' and then check the class using the .is_a() method. In most cases, properties will be found as IfcPropertySingleValue and in that case, the .NominalValue.wrappedValue returns the value, which we pack in a string.

We return an indented string and add the .Name attribute along with the value and the unit.

# PropertySet
def print_element_properties(property_set, level):
    indent(level), print(property_set.Name)
    for prop in property_set.HasProperties:
        unit = str(prop.Unit) if hasattr(prop, 'Unit') else ''
        prop_value = '<not handled>'
        if prop.is_a('IfcPropertySingleValue'):
            prop_value = str(prop.NominalValue.wrappedValue)
        indent(level + 1)
        print(str('{0} = {1} [{2}]').format(prop.Name, prop_value, unit))

When we test the code now on the IfcOpenHouse.ifc example file, we see that our site contains a property set called Pset_SiteCommon with a single property called TotalArea, a value of 523.31397507897 and a unit of None.

#12 = IfcProject "IfcOpenHouse" (02NgJh73z9H9p1e8asU8xp)
.  #18 = IfcSite "None" (3ouLTcA3r8P9cplyI5HVtV)
.  .  Pset_SiteCommon
.  .  .  TotalArea = 523.31397507897 [None]
.  .  #25 = IfcBuilding "None" (2CLK0Xn_T2iBVjws$YeMlK)
.  .  .  #32 = IfcBuildingStorey "None" (15VFoi2F54guzs2VUHeW$Z)
.  .  .  .  #34 = IfcWallStandardCase "South wall" (2eZ4QSizXEuRNpHMBut_dk)
.  .  .  .  #77 = IfcFooting "Footing" (2YmZTDa0T3h97PCthS2e$n)
(continued)

Method to display quantities

A very similar method is created to display quantities. It has the same structure, but has to look for different attributes. The set of quantities is like a property set, but is retrieved from the .Quantities attribute, which can contain multiple quantities. We have prepared a series of tests to check the quantity class and distinguish between Length, Area, Volume and Count quantities, as their actual quantity value is always in a different attribute: .LengthValue .AreaValue .VolumeValue and .CountValue

# QuantitySet
def print_element_quantities(quantity_set, level):
    indent(level), print(quantity_set.Name)
    # the individual quantities
    for quantity in quantity_set.Quantities:
        unit = str(quantity.Unit) if hasattr(quantity, 'Unit') else ''
        quantity_value = '<not handled>'
        if quantity.is_a('IfcQuantityLength'):
            quantity_value = str(quantity.LengthValue)
        elif quantity.is_a('IfcQuantityArea'):
            quantity_value = str(quantity.AreaValue)
        elif quantity.is_a('IfcQuantityVolume'):
            quantity_value = str(quantity.VolumeValue)
        elif quantity.is_a('IfcQuantityCount'):
            quantity_value = str(quantity.CountValue)
        indent(level + 1)
        print(str('{0} = {1} [{2}]').format(quantity.Name, quantity_value, unit))

Running the script now returns a full listing of the Spatial hierarchy, with all defined types, property sets and quantity sets.

#12 = IfcProject "IfcOpenHouse" (02NgJh73z9H9p1e8asU8xp)
.  #18 = IfcSite "None" (3ouLTcA3r8P9cplyI5HVtV)
.  .  Pset_SiteCommon
.  .  .  TotalArea = 523.31397507897 [None]
.  .  #25 = IfcBuilding "None" (2CLK0Xn_T2iBVjws$YeMlK)
.  .  .  #32 = IfcBuildingStorey "None" (15VFoi2F54guzs2VUHeW$Z)
.  .  .  .  #34 = IfcWallStandardCase "South wall" (2eZ4QSizXEuRNpHMBut_dk)
.  .  .  .  #77 = IfcFooting "Footing" (2YmZTDa0T3h97PCthS2e$n)
.  .  .  .  #187 = IfcRoof "Roof" (2BBRbyGfz0oAPIZfmijoFU)
.  .  .  .  .  #188 = IfcSlab "South roof" (2N9x4fcqf7jPAPjLJiEAeu)
.  .  .  .  .  #189 = IfcSlab "North roof" (0XohIE5VnFEeXM8xcyKinZ)
.  .  .  .  #219 = IfcWallStandardCase "North wall" (3MI1ZgnBDDDBiVLjK3Pjw6)
.  .  .  .  #292 = IfcWallStandardCase "East wall" (2BwdmwOJTA3AXuw7xnEWxI)
.  .  .  .  #299 = IfcWallStandardCase "West wall" (2NGpkeicv1wxe44d6RlRW4)
.  .  .  .  #2609 = IfcStairFlight "None" (3Kt_zAPyX1DPS4ICfjKmJe)
.  .  .  .  #2687 = IfcDoor "None" (2yg42GmAP2FPvawq2Wx0_t)
.  .  .  .  .  #2697 = IfcDoorStyle "Door type" (3wdEz$iKfBfQfZvy43$ION)
.  .  .  .  #2756 = IfcWindow "None" (1I6DWQZ398jPufhllK9p9v)
.  .  .  .  .  #2787 = IfcMember "None" (3AB2FCwEbFJRjTNrrzBzRx)
.  .  .  .  .  #2793 = IfcMember "None" (1NOhpy0kH7GhQhwTkNsTkW)
.  .  .  .  .  #2804 = IfcMember "None" (0wIkMD7fX4Gx1TviaIBg7B)
.  .  .  .  .  #2810 = IfcMember "None" (3bbwac$Qz9XfP4$vwRZn_n)
.  .  .  .  .  #2830 = IfcPlate "None" (3vj_YCbVX0xf$s9BJ1jfni)
.  .  .  .  #2837 = IfcWindow "None" (1CDtdlyp142Ra7nrHrHp3n)
.  .  .  .  .  #2863 = IfcMember "None" (0bYSqmJFH9B9iQineRza0t)
.  .  .  .  .  #2869 = IfcMember "None" (0iEQdvyZbCD9QwMyZcoWbe)
.  .  .  .  .  #2875 = IfcMember "None" (0QnU_5Wv10efdpZOlt_PdH)
.  .  .  .  .  #2881 = IfcMember "None" (2Rr$NdMOj5ehIfklzFoux3)
.  .  .  .  .  #2901 = IfcPlate "None" (2vbNIVlI50GO0a4IjZdaws)
.  .  .  .  #2908 = IfcWindow "None" (3R3qWEaU905At4mDtVMMcL)
.  .  .  .  .  #2934 = IfcMember "None" (1r8BKeb7D99xtMg_MHX9gH)
.  .  .  .  .  #2940 = IfcMember "None" (1aOuLtEW9C1gu921pG7NmQ)
.  .  .  .  .  #2946 = IfcMember "None" (2wcyJCbj99oA87M9J1$pwo)
.  .  .  .  .  #2952 = IfcMember "None" (1Jfizzbvb9vOzFvdiixKEY)
.  .  .  .  .  #2972 = IfcPlate "None" (2JKY$Y_Af9g9P3eOabCTlE)
.  .  .  .  #2979 = IfcWindow "None" (1rIP5j0CrBBgIVVNROt1U8)
.  .  .  .  .  #3005 = IfcMember "None" (3IkQrRLgTEmgIYqQ6CYj5j)
.  .  .  .  .  #3011 = IfcMember "None" (131EGjUgnE1eBcsIJj2NWq)
.  .  .  .  .  #3017 = IfcMember "None" (0a$SU8COT7dQTEqIBq1iIm)
.  .  .  .  .  #3023 = IfcMember "None" (0jHdJ0WUH0OPZ7Tu5W_b70)
.  .  .  .  .  #3043 = IfcPlate "None" (1SDocEeHH9uBO_5OZEJJ9h)
.  .  .  .  #3050 = IfcWindow "None" (1dmSFt7FTBjwBVhxk9UIwv)
.  .  .  .  .  #3076 = IfcMember "None" (1J1PQJXqz6oPlCr02UnY5V)
.  .  .  .  .  #3082 = IfcMember "None" (39v3BUiu1EnAY67Ev89uee)
.  .  .  .  .  #3088 = IfcMember "None" (0LIKNCiHr4pgJvgjnj77Ue)
.  .  .  .  .  #3094 = IfcMember "None" (2wTCMQ71P1ovhnMbcvrSa8)
.  .  .  .  .  #3114 = IfcPlate "None" (1QRDIGdcjDhxuxkcU7tT1Z)

The Open House example file does not contain quantities, but from another file we see that the script picks them up, here with the name BaseQuantities:

.  .  .  .  #93324 = IfcCovering "41_Gevelafwerking_Balk-x" (1tfiwvX6LiHe$wvd7ntvew)
.  .  .  .  .  #81427 = IfcCoveringType "Deksteen 50 x 5/9,5 500 x 92" (0vsMyV9dKq55fJB9JIkeqx)
.  .  .  .  .  Pset_CoveringCommon
.  .  .  .  .  .  FireRating = n.v.t. [None]
.  .  .  .  .  .  FragilityRating = n.v.t. [None]
.  .  .  .  .  .  Material = Deksteen 50 x 5/9,5 [None]
.  .  .  .  .  .  Combustible = False [None]
.  .  .  .  .  Pset_FireRatingProperties
.  .  .  .  .  .  FireResistanceRating = n.v.t. [None]
.  .  .  .  .  Pset_ManufacturerOccurrence
.  .  .  .  .  .  BarCode = n.v.t. [None]
.  .  .  .  .  .  SerialNumber = n.v.t. [None]
.  .  .  .  .  Pset_ManufacturerTypeInformation
.  .  .  .  .  .  ModelReference = n.v.t. [None]
.  .  .  .  .  .  Manufacturer = n.v.t. [None]
.  .  .  .  .  .  ProductionYear = DD/MM/YYYY [None]
.  .  .  .  .  BaseQuantities
.  .  .  .  .  .  GrossArea = 6.48599996724 [None]
.  .  .  .  .  .  NetArea = 6.48599996724 [None]