Hello, world!¶
This is the C++ counterpart of the Python Hello, world! crash course. It guides you using the IfcOpenShell C++ API at a high level.
The examples use the IFC4 schema, so that the generated Ifc4 classes can be
used by name, such as Ifc4::IfcWall. The C++ core is not tied to a single
schema though, see Schema-agnostic parsing of IFCs for how to write the same logic in a way that
processes all schema versions at once.
Note
Each example takes the path of the model as its first command line
argument, and the snippets in this page assume that it has been loaded into
a variable called model, exactly like the Python crash course assumes.
Loading the model¶
The equivalent of Python’s ifcopenshell.open() is the ifcopenshell::file
constructor. A file that could not be read reports as not good, which is how you
check that parsing succeeded.
// The path is normally the path to your model, here it is taken from the
// command line so that this example can actually be run.
std::string input_file_path = argv[1];
ifcopenshell::file model(input_file_path);
if (!model.good()) {
std::cerr << "Unable to parse .ifc file" << std::endl;
return 1;
}
Inspecting the schema¶
Note that contrary to IfcOpenShell-Python in C++ there is no file.schema_identifier(), only file.schema() which returns the verbatim string from the underlying express schema.
// May return IFC2X3, IFC4, or IFC4X3_ADD2.
std::cout << model.schema()->name() << std::endl;
Getting an instance by ID¶
Every IFC entity instance in an IFC-SPF file has a STEP ID, such as #1.
// In IFC-SPF every instance has a STEP ID, such as #1. The returned
// instance is falsy when there is no such instance in the file.
auto instance = model.instance_by_id(1);
if (instance) {
instance.to_string(std::cout);
std::cout << std::endl;
}
Getting an instance by GlobalId¶
Getting data from beginning to end is not too meaningful to humans, but a
GlobalId is.
// Getting data from beginning to end is not meaningful to humans, but a
// GlobalId is. The returned instance is falsy when there is no instance
// with that GlobalId in the file.
auto instance = model.instance_by_guid("0EI0MSHbX9gg8Fxwar7lL8");
if (instance) {
instance.to_string(std::cout);
std::cout << std::endl;
}
Counting the instances of a type¶
instances_by_type() returns all instances of an entity type, including
subtypes, so IfcWallStandardCase instances are returned for IfcWall as
well.
// Note that instances_by_type also returns subtypes, for example
// IfcWallStandardCase instances are returned for IfcWall as well.
auto walls = model.instances_by_type<Ifc4::IfcWall>();
std::cout << walls.size() << std::endl;
The class of an instance¶
Once we have an instance we can ask it what it is.
auto wall = walls.front();
// Returns 'IfcWall'
std::cout << wall.declaration().name() << std::endl;
You can also test against other classes, including parent classes.
auto wall = walls.front();
// An instance is also an instance of all of its supertypes, so a wall is
// an IfcElement, but not an IfcWindow. Note how this works against the
// declarations, which is what makes it usable for any schema version.
const ifcopenshell::declaration& type = wall.declaration();
std::cout << (type.is("IfcWall") ? "true" : "false") << std::endl;
std::cout << (type.is("IfcElement") ? "true" : "false") << std::endl;
std::cout << (type.is("IfcWindow") ? "true" : "false") << std::endl;
The STEP ID of an instance¶
auto wall = walls.front();
std::cout << wall.id() << std::endl;
Reading attributes¶
IFC attributes have a particular order, and can be addressed by their position just like a list.
// IFC attributes have a particular order and can be read positionally.
// Both indices below are known to hold a string, other attribute types
// are converted to their C++ equivalent, such as int64_t or double.
auto wall = walls.front();
// The first attribute is the GlobalId.
std::cout << static_cast<std::string>(wall.get_attribute_value(0)) << std::endl;
// The third attribute is the Name - note that this will fail when the attribute is unset!
std::cout << static_cast<std::string>(wall.get_attribute_value(2)) << std::endl;
Knowing the order of attributes is boring and technical, so the generated classes
also have accessors named after the attribute, which are strongly typed and
which wrap optional attributes in a std::optional.
// Knowing the order of attributes is not necessary: the generated classes
// have accessors named after the attribute, strongly typed and wrapped in
// std::optional when the attribute is optional.
auto wall = walls.front();
std::cout << wall.GlobalId() << std::endl;
std::cout << wall.Name().value_or("<unnamed>") << std::endl;
Reading the property sets of an instance¶
Python has ifcopenshell.util.element.get_psets(), but the C++ core does not
ship a ready made property set helper, so the relationships are walked by hand.
// Unlike ifcopenshell.util.element.get_psets() in Python, the C++ core
// does not ship a ready made property set helper, so the relationships are
// walked by hand. IsDefinedBy() lists the relationships, and the property
// set itself is reached with RelatingPropertyDefinition().
auto wall = walls.front();
for (auto& relationship : wall.IsDefinedBy()) {
auto definition = relationship.RelatingPropertyDefinition();
if (auto pset = definition.as<Ifc4::IfcPropertySet>()) {
pset.to_string(std::cout);
std::cout << std::endl;
for (auto& property : pset.HasProperties()) {
property.to_string(std::cout);
std::cout << std::endl;
}
}
// beware: definition can also be a Quantity Set, or a Predefined Property Set, or
// from IFC4 onwards a IfcPropertySetDefinitionSet (a set of the above)
}
Inverse attributes¶
Some attributes are special, and are called “inverse attributes”. They happen when another instance is referencing our instance, for example to define a relationship. Just treat them like regular attributes.
// Inverse attributes are attributes that other instances point at, for
// example to define a relationship, to void our wall, or to add a
// quantity take-off value to it. Just treat them like regular attributes.
auto wall = walls.front();
for (auto& relationship : wall.IsDefinedBy()) {
relationship.to_string(std::cout);
std::cout << std::endl;
}
Perhaps we want to see all instances which are referencing our instance, regardless of which attribute they use to do so.
// Perhaps we want to see all instances which are referencing our wall,
// regardless of which attribute they use to do so. This is the equivalent
// of Python's file.get_inverse() and takes a STEP ID.
auto wall = walls.front();
for (auto& instance : model.instances_by_reference(static_cast<int>(wall.id()))) {
instance.to_string(std::cout);
std::cout << std::endl;
}
Traversing references¶
The opposite of the previous example: everything our instance references.
// The opposite of get_inverse(): a depth first traversal of everything
// our wall references. Note that the root instance itself is included.
auto wall = walls.front();
for (auto& instance : ifcopenshell::file::traverse(wall)) {
instance.to_string(std::cout);
std::cout << std::endl;
}
// Or, let's just go down one level deep. A max_depth of 1 gives the
// instances that are referenced by the wall itself.
for (auto& instance : ifcopenshell::file::traverse(wall, 1)) {
instance.to_string(std::cout);
std::cout << std::endl;
}
Modifying data¶
To modify data, assign it to the relevant attribute.
// To modify data, assign it to the relevant attribute. Changes are made
// in memory only, until the file is written out again.
auto wall = walls.front();
wall.setName(std::string("My new wall name"));
std::cout << wall.Name().value_or("<unnamed>") << std::endl;
You can also generate a new GlobalId.
// A default constructed IfcOpenShell::global_id is a new, random,
// compressed GlobalId, and converts to a std::string.
auto wall = walls.front();
ifcopenshell::global_id new_global_id;
wall.setGlobalId(new_global_id);
std::cout << wall.GlobalId() << std::endl;
Writing the model¶
After modifying some IFC data, you can save it to a new IFC-SPF file.
// After modifying some IFC data, the file can be saved to a new IFC-SPF
// file. All of the serialisation happens in the stream operator.
std::ofstream output("/path/to/a/new.ifc");
output << model;
Creating a new file¶
You can generate a new IFC file from scratch too, instead of reading an existing one. Such a file is in memory only until it is written out.
// A new file can be created from scratch instead of reading an existing
// one. Without arguments this is an IFC4 file.
ifcopenshell::file model;
// Or if you want a particular schema:
ifcopenshell::file model_2x3(ifcopenshell::schema_by_name("IFC2X3"));
Creating instances¶
You can create new IFC instances, and they are added to the file that created them straight away.
// New instances are created by the file that owns them, and are added to
// it straight away. Note how all of the attributes are blank.
auto new_wall = model.create<Ifc4::IfcWall>();
new_wall.to_string(std::cout);
std::cout << std::endl;
// ... and the file now contains it.
std::cout << model.instances_by_type<Ifc4::IfcWall>().size() << std::endl;
Alternatively, you can also create an instance from the name of the entity as it
appears in the IFC schema, which is the equivalent of Python’s
create_entity().
// Alternatively, an instance can be created from the name of the entity as
// it appears in the IFC schema. This is the equivalent of Python's
// file.create_entity() and, unlike the previous example, does not require
// the schema to be known at compile time.
auto new_wall = model.create(model.schema()->declaration_by_name("IfcWall"));
new_wall.to_string(std::cout);
std::cout << std::endl;
Attributes can be filled in straight away, by their position, in the order of the attributes.
// Attributes can be filled in as the instance is created, in the order of
// the attributes. The first attribute of an IfcRoot is the GlobalId.
auto new_wall = model.create(model.schema()->declaration_by_name("IfcWall"));
ifcopenshell::global_id new_global_id;
new_wall.set_attribute_value(0, static_cast<const std::string&>(new_global_id));
new_wall.to_string(std::cout);
std::cout << std::endl;
Again, knowing the order of attributes is difficult, so attributes can also be assigned by name.
// Knowing the order of attributes is not necessary: they can also be
// assigned by name, using the name from the IFC schema.
auto new_wall = model.create(model.schema()->declaration_by_name("IfcWall"));
ifcopenshell::global_id new_global_id;
new_wall.set_attribute_value("GlobalId", static_cast<const std::string&>(new_global_id));
new_wall.set_attribute_value("Name", std::string("Wall Name"));
new_wall.to_string(std::cout);
std::cout << std::endl;
Sometimes it is easier to collect the attributes in a table first, and assign
them in a loop. This is the equivalent of expanding a Python dictionary into
create_entity().
// Sometimes it is easier to collect the attributes in a table first, and
// assign them in a loop. This is the equivalent of expanding a Python
// dictionary into create_entity().
std::vector<std::pair<std::string, std::string>> attributes = {
{"GlobalId", static_cast<const std::string&>(ifcopenshell::global_id())},
{"Name", "Wall Name"},
};
auto new_wall = model.create(model.schema()->declaration_by_name("IfcWall"));
for (auto& attribute : attributes) {
new_wall.set_attribute_value(attribute.first, attribute.second);
}
new_wall.to_string(std::cout);
std::cout << std::endl;
Some attributes of an instance are not text, but a reference to another instance. The generated setters take the referenced instance directly.
// Some attributes are not text but a reference to another instance. The
// generated setters take the instance directly, so all that is needed is
// another instance in the same file.
auto wall = walls.front();
auto owner_history = model.create<Ifc4::IfcOwnerHistory>();
wall.setOwnerHistory(owner_history);
wall.to_string(std::cout);
std::cout << std::endl;
Copying an instance into another file¶
What if we already have an instance in one file, and want to add it to another? The forward references of the instance are copied along with it, and the copy is created in the schema of the target file, so both files have to use the same one.
// Instances can be copied from one file into another. The forward
// references of the instance are copied along with it, and the copy is
// created in the target schema, so both files have to use the same one.
auto wall = walls.front();
ifcopenshell::file new_model(model.schema());
new_model.add_entity(wall);
for (auto& instance : new_model.instances_by_type<Ifc4::IfcWall>()) {
instance.to_string(std::cout);
std::cout << std::endl;
}
Note that, unlike Python’s ifcopenshell.util.element.copy(), this does copy
references recursively, but it makes no other attempts at resulting in a valid
file.
Removing an instance¶
Fed up with an instance? Remove it.
// Instances that are no longer needed can be removed. Attributes of other
// instances that point at the removed instance are unset, so that the
// file stays consistent.
auto wall = walls.front();
model.remove_entity(wall);
std::cout << model.instances_by_type<Ifc4::IfcWall>().size() << std::endl;