Assignment 3-resit
BIM to Geo conversion using Boolean set operations on Nef polyhedra
Deadline is 21 June 2024 at 09:00 am
Late submission? 10% will be removed for each day that you are late.
You’re allowed for this assignment to work in a group of 3 (and thus submit only one solution for all of you). If you prefer to work alone or in pairs, it’s also fine.
The assignment is worth 20% of your final mark.
- Overview
- How to get started
- 1. From IFC to OBJ
- 2. From OBJ to Nef polyhedra
- 3. The big Nef polyhedron
- 4. Extracting geometries
- 5. Writing CityJSON
- Deliverables
- Marking
Overview
The aim of this assignment is to implement a BIM to Geo conversion for one building. In short, this will involve five steps:
- converting the BIM model to OBJ using
IfcConvert
; - loading each object in the OBJ file(s) into a CGAL Nef polyhedron;
- processing the Nef polyhedra into a single big Nef polyhedron representing the space filled by the building’s floors, walls, windows and so on;
- extracting the geometries representing the building’s exterior surface and individual rooms;
- writing the geometries to a CityJSON file with correct semantics.
Everything should be implemented in C++ with the help of any packages from the CGAL library. You are also free to use any other external libraries, but only for reading and writing files, such as Niels Lohmann’s JSON for Modern C++ library.
How to get started
There is no sample code to download for this lesson, but we provide some tips and code snippets in the description of each step below.
To start developing your code, we recommend that you start with a very simple IFC file, such as the IfcOpenHouse. This will be useful to get started and to debug your code later on.
However, to get a full mark for the assignment, your code should be able to process a reasonably complex IFC model of your choice. We suggest the Duplex Apartment or one of the models made by KIT (those starting with AC). You can find other models in the OpenIFC model repository, this repository from buildingSMART or this one from the developers of BIMserver. You can view the models with an IFC viewer, such as Solibri Anywhere, the Open IFC Viewer or the BlenderBIM add-on for Blender.
1. From IFC to OBJ
The first step is to convert the BIM model in IFC to an OBJ file (or several) containing the relevant parts of the building. It is up to you to decide which parts are relevant for the conversion.
For the conversion, you should use IfcConvert
, which is part of the IfcOpenShell library. In order to select the relevant parts, you likely want to use the --include
or --exclude
options. If the filtering is not good enough, you might also want to use an option like --use-element-names
to ensure you have access to the original element names (or something similar with IDs or materials).
Check out how to use the options available in IfcConvert
by running it with the --help
option.
2. From OBJ to Nef polyhedra
First, parse the OBJ files generated by IfcConvert
using your own code, loading each object into memory. In the OBJ files generated by IfcConvert
, objects are denoted by the g
(group) marker. These can have multiple shells, which are denoted by different materials (the usemtl
marker). Check this Wikipedia article if you need help understanding the OBJ files.
You probably want to create your own simple data structures to load the data into. These could look something like this:
std::vector<Kernel::Point_3> points;
struct Face {
std::vector<unsigned long> vertices; // indices in vector of points
};
struct Shell {
std::vector<Face> faces;
};
struct Object {
std::string id;
std::vector<Shell> shells;
};
std::map<std::string, Object> objects;
Next, load each shell into a CGAL Polyhedron_3
using the Polyhedron_incremental_builder_3
. In order to use the Polyhedron_incremental_builder_3
, you need to create a custom struct
or class
like this one:
template <class HDS>
struct Polyhedron_builder : public CGAL::Modifier_base<HDS> {
std::vector<Point> vertices;
std::vector<std::vector<unsigned long>> faces;
Polyhedron_builder() {}
void operator()(HDS& hds) {
CGAL::Polyhedron_incremental_builder_3<HDS> builder(hds, true);
std::cout << "building surface with " << vertices.size() << " vertices and " << faces.size() << " faces" << std::endl;
builder.begin_surface(vertices.size(), faces.size());
for (auto const &vertex: vertices) builder.add_vertex(vertex);
for (auto const &face: faces) builder.add_facet(face.begin(), face.end());
builder.end_surface();
}
};
Which you use by defining an instance of Polyhedron_builder_3
. Add a shell’s vertices and faces to it, then call the builder using the delegate
function. It will look something like this:
Polyhedron polyhedron;
Polyhedron_builder<Polyhedron::HalfedgeDS> polyhedron_builder;
for (auto const &face: ...) {
polyhedron_builder.faces.emplace_back();
for (auto const &vertex: face.vertices) {
polyhedron_builder.vertices.push_back(...);
polyhedron_builder.faces.back().push_back(...);
}
} polyhedron.delegate(polyhedron_builder);
Note that the polyhedron builder (which does something similar to what you did in hw1!) is a bit picky. It only works well if the vertices are unique (ie faces sharing a vertex should refer to the same index in the vertices list), whereas the OBJ files generated by IfcConvert
repeat vertex coordinates. It also doesn’t like inconsistently oriented input. It is up to you to deal with these issues before passing the input to Polyhedron_builder_3
.
Finally, after you have a Polyhedron_3
, you can easily create a Nef_polyhedron_3
from it using the interface between the two, as long as the polyhedron is closed (ie polyhedron.is_closed()
). For this, you do something like:
Nef_polyhedron nef_polyhedron(polyhedron);
3. The big Nef polyhedron
This is the most open part of the assignment. In general terms, we expect you to apply CSG operations to merge the individual Nef polyhedra into one big Nef polyhedron containing all the space that is filled by a building’s floors, walls, windows and so on.
At the end of this process, you should ideally have something with one exterior shell (the outer surface of the building) and a few inner shells (one for each room).
We recommend you to have a look at the Boolean point set operations available in CGAL’s Nef polyhedra. In addition, you might want to have a look at the 3D Minkowski sum package, since you can use it to fill in small gaps between objects. Feel free to come up with other strategies (eg moving points around).
4. Extracting geometries
Starting from the single big Nef polyhedron, you should navigate through the Nef_polyhedron_3
structure to extract the geometries of the different shells and do some final processing. The aim of this step should be to obtain a set of simpler geometries with semantics.
Thinking of using the interface between Nef_polyhedron_3
and Polyhedron_3
? It’s probably a bad idea, since this only works if the Nef polyhedron is simple (nef_polyhedron.is_simple()
), which your Nef polyhedron shouldn’t be.
In order to navigate through the (quite complex) Nef_polyhedron_3
structure, CGAL provides a set of useful macros. For this assignment, the most useful ones are CGAL_forall_volumes
and CGAL_forall_shells_of
. To use the latter one, you need to define a custom struct
or class
like this one:
struct Shell_explorer {
std::vector<Point> vertices;
std::vector<std::vector<unsigned long>> faces;
void visit(Nef_polyhedron::Vertex_const_handle v) {}
void visit(Nef_polyhedron::Halfedge_const_handle he) {}
void visit(Nef_polyhedron::SHalfedge_const_handle she) {}
void visit(Nef_polyhedron::SHalfloop_const_handle shl) {}
void visit(Nef_polyhedron::SFace_const_handle sf) {}
void visit(Nef_polyhedron::Halffacet_const_handle hf) {
... // do something to each half-face of a shell
}
};
Such a structure is meant to follow what is called a visitor pattern, where each function will be called once per each element in the shell. Meanwhile, the main code could look something like this:
Nef_polyhedron::Volume_const_iterator current_volume;
CGAL_forall_volumes(current_volume, big_nef) {
Nef_polyhedron::Shell_entry_const_iterator current_shell;
CGAL_forall_shells_of(current_shell, current_volume) {
Shell_explorer se;
Nef_polyhedron::SFace_const_handle sface_in_shell(current_shell);
big_nef.visit_shell_objects(sface_in_shell, se);
...
}
}
If you have more volumes and shells than you expected in your big Nef polyhedron, check this explanation. Check this figure to understand how to navigate the data structure used in Nef polyhedra (SNC).
As you extract the geometries of the different shells, try to keep track of their meaning when possible (eg whether they are part of the exterior surface or the inner surface of a room). Also, you should attempt to derive the other semantics required by CityJSON (eg whether an exterior surface is a RoofSurface
, WallSurface
or GroundSurface
). For this, you can use a surface’s properties, such as its location or normal vector.
5. Writing CityJSON
Starting from the simple geometries with semantics created by the previous step, you should create a CityJSON 2.0 file containing them. This file should have several city objects: one Building
and a few instances of BuildingRoom
. These should follow the CityJSON specifications.
As for the writing of the file, it can be done manually or through a JSON library, such as Niels Lohmann’s JSON for Modern C++ library. Using this library, you have to create something like this:
#include "json.hpp"
nlohmann::json json;
json["type"] = "CityJSON";
json["version"] = "2.0";
json["transform"] = nlohmann::json::object();
json["transform"]["scale"] = nlohmann::json::array({1.0, 1.0, 1.0});
json["transform"]["translate"] = nlohmann::json::array({0.0, 0.0, 0.0});
json["CityObjects"] = nlohmann::json::object();
...
std::string json_string = json.dump(2);
std::ofstream out_stream("mybuilding.json");
out_stream << json_string;
out_stream.close();
Deliverables
You have to submit a ZIP file (or another compressed format) with:
- the original IFC file that you chose to convert,
- the intermediate OBJ file(s) generated by
IfcConvert
, - the CityJSON file with the results of your conversion,
- a report in PDF format (not in Microsoft Word),
- the code you wrote to do the assignment, and
- a Readme file that explains what all the files submitted are and how I should run the code.
Submit your files by uploading them here.
1. Input IFC file
Likely the same as what you downloaded. If you had to modify it for your code to work, document it in the report and submit your modified version.
2. Intermediate OBJ files
Likely the file(s) generated by IfcConvert
. If you had to modify them for your code to work, document it in the report and submit your modified versions.
3. Output CityJSON file
The file should be produced with your own code. If specific settings/parameters are required to produce it, explain it in the Readme.
4. Report
You need to submit a simple report outlining your work. It should document the main steps in your conversion methodology, explain briefly how you implemented them, and why you chose to implement them in this manner. Focus on high-level descriptions of how things work and the engineering/design choices that you made, not on explaining your code line by line. Discuss the pros and cons of your method, assess the quality of your results and the performance of your code. We don’t expect perfect results—just document honestly what works well and what doesn’t.
Use a small section of your report to document how the tasks were divided among the members of the team. The workload needs to be relatively equal.
We expect maximum 10 pages for the report (figures and graphs included). Don’t forget to write your names.
5. Code
You should submit all the files that I need to run your code. The code should read the input OBJ file(s) and output the converted CityJSON file. Everything should be implemented in C++ using only the following libraries: CGAL, anything used only to read and write files, and everything from the C++ Standard Library.
6. Readme
It should: (i) describe all files submitted, and (ii) tell us how to run the code. Provide enough instructions so that we can generate exactly the CityJSON file uploaded from the OBJ files submitted.
Marking
Criterion | Points |
---|---|
followed all rules | 1 |
quality of results | 5 |
quality of report | 4 |
[last updated: 2024-04-16 16:46]