Getting Started with IFC parsing

The basis of all parsing and getting information from the IFC starts with obtaining a IfcParse::IfcFile object and validating that it is good for use.

std::string input_file_path = "/path/to/my/model.ifc";
IfcParse::IfcFile file(input_file_path);
if (!file.good()) {
  std::cerr << "Unable to parse .ifc file" << std::endl;
  return 1;
}

Schema-agnostic parsing of IFCs

It is advisable to design your programme in a schema-agnostic fashion to be able to process all schema versions of input IFCs. While different options are presented here, the most comfortable approach for modern C++ developers would be with the use of templates as shown below.

// necessary includes

// For BOOST_PP_SEQ_FOR_EACH and BOOST_PP_STRINGIZE preprocessor macro
#include <boost/preprocessor/seq/for_each.hpp>
// Include all possible schema types that could be parsed
#include "ifcparse/Ifc2x3.h"
#include "ifcparse/Ifc4.h"
#include "ifcparse/Ifc4x3_add2.h"

#define IFC_SCHEMA_SEQ (4x3_rc2)(4)(2x3) // TODO: Enumerate through all IFC schemas you want to be able to process
#define EXPAND_AND_CONCATENATE(elem) Ifc##elem
#define PROCESS_FOR_SCHEMA(r, data, elem) if (schema_version == BOOST_PP_STRINGIZE(elem)) { parseIfc<EXPAND_AND_CONCATENATE(elem)>(file); } else

template<typename Schema>
void parseIfc(IfcParse::IfcFile &file) {
    const typename Schema::IfcProduct::list::ptr elements = file.instances_by_type<typename Schema::IfcProduct>();
    for (typename Schema::IfcProduct::list::it it = elements->begin();
        it != elements->end(); ++it) {
        Schema::IfcProduct *ifcProduct = *it;
        // TODO: Do something with ifcProduct
    }
}

void process(const std::string &schema_version, IfcParse::IfcFile &file) {
    // Syntactic sugar for iterating through all IFC schemas and passing them to main processing method
    BOOST_PP_SEQ_FOR_EACH(PROCESS_FOR_SCHEMA, ,IFC_SCHEMA_SEQ)
    { // The final else to catch unhandled schema version
        throw std::invalid_argument("IFC Schema " + schema_version + " not supported");
    }
}

int main(int argc, char* argv[]) {
    // file of IfcParse::IfcFile previously defined
    auto schema_version = file.schema()->name();
    schema_version = schema_version.substr(3);
    std::transform(schema_version.begin(), schema_version.end(), schema_version.begin(), [](unsigned char c) { return std::tolower(c); });
    process(schema_version, file);
    // TODO: Handle more cases of IFC schema versions here
}

Reading out attributes of an IfcProduct

The properties of an IfcProduct, and by extension any derived class, can be read as by calling the method of the same name, e.g. GlobalId(), Name(). Note that optional properties like name, long name, or description, among others, are wrapped with a boost::optional (documentation) container object. Except for pointer types such as IfcRoot::OwnerHistory, here the developer is still responsible for performing comparison to nullptr before using the value.

Defensive programming with IfcOpenshell

The need for (down-)casting object pointers when accessing various properties in an IFC entity is evident from the previous code sample as the methods and properties usually return the abstract class of the entity. It is hence important to check for nullptr when performing such casts. The existence of certain fields and properties should also be checked. Also note that IfcOpenShell has as of now not been tested explicitly against malicious inputs. Schema validation (the correctness of attribute types and conformance to schema where rules) can currently only be assessed in Python (using ifcopenshell.validate –rules).