├── LICENSE
├── README.md
├── __init__.py
├── adjacent_rooms_graph_test.dot
├── adjacent_rooms_graph_test.png
├── ifc2graph
├── __init__.py
├── __pycache__
│ ├── IfcGeometry.cpython-37.pyc
│ ├── SpaceGeometryContainer.cpython-37.pyc
│ ├── __init__.cpython-37.pyc
│ ├── extract_adjacent_rooms_from_ifc.cpython-37.pyc
│ ├── extract_geometry_from_ifc.cpython-37.pyc
│ ├── graph.cpython-37.pyc
│ └── ifcgraph.cpython-37.pyc
├── ifcgeometry.py
├── ifcgraph.py
└── utils
│ ├── Timer.py
│ ├── __init__.py
│ ├── __pycache__
│ ├── __init__.cpython-37.pyc
│ └── progressbar.cpython-37.pyc
│ └── progressbar.py
├── setup.py
├── test.py
└── test_ifc_files
├── Office Building.ifc
└── Residential House.ifc
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright (c) 2022, JBjoernskov
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ifc2Graph
2 |
3 | The Ifc2Graph package can be used as a tool to extract space topology from IFC-files.
4 | Ifc2Graph currently has two functionalities, divided into the classes IfcGeometry and IfcGraph.
5 |
6 | ## Installation
7 |
8 | The package has been tested for Python 3.7.12, but should also work for other 3.7.X versions.
9 |
10 | The package can be install with pip and git as follows:
11 | ```bat
12 | python -m pip install git+https://github.com/JBjoernskov/Ifc2Graph
13 | ```
14 |
15 | To utilize the ifcgraph module, Graphviz must be installed separately:
16 | - [Graphviz](https://graphviz.org/download) (Remember to add the directory to system path)
17 |
18 |
19 | ## Usage
20 |
21 |
22 | ### IfcGeometry
23 | The IfcGeometry class extracts the geometry for each IfcSpace entity in the provided IFC-file using [IfcOpenShell](http://ifcopenshell.org/python). This geometry is used to instantiate [Trimesh](https://trimsh.org) objects, which are then used in combination with [NumPy](https://numpy.org) to process the geometry and determine the adjacency of rooms.
24 |
25 | #### Input:
26 | As input a path to a valid IFC-file must be provided. Example files are found in the "test_ifc_files" folder.
27 |
28 |
29 |
30 |
31 |
32 |
33 | [Source](https://www.ifcwiki.org/index.php?title=KIT_IFC_Examples):
34 | *Institute for Automation and Applied Informatics (IAI) / Karlsruhe Institute of Technology (KIT)*
35 |
36 | #### Ouput:
37 | The output is a dictionary with keys corresponding to the Name attribute of the IfcSpace entities contained in the IFC input file.
38 | The values of this dictionary are lists containing the Name attributes of the adjacent IfcSpace entities.
39 |
40 |
41 | ### IfcGraph
42 | The IfcGraph class visualizes the obtained adjacency graph using [NetworkX](https://networkx.org) to construct the graph and [Graphviz](https://graphviz.org) to visualize the the graph.
43 |
44 | #### Input:
45 | As input, the previously obtained dictionary must be provided.
46 |
47 | #### Ouput:
48 | The output is an image of the generated graph.
49 | Below, examples of generated graph images is seen for an actual building.
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | ## Example
59 | The test.py file shows the basic use of the package.
60 |
61 |
62 |
63 | ## Cite as
64 | ```yaml
65 | @inproceedings{Ifc2Grap},
66 | title = {A Modular Thermal Space Coupling Approach for Indoor Temperature Forecasting Using Artificial Neural Networks",
67 | abstract = {With the increasing digitalization of buildings and the adoption of comprehensive sensing and metering networks, the concept of building digital twins is emerging as a key component in future smart and energy-efficient buildings. Such digital twins enable the use of flexible and adaptable data-driven models to provide services such as automated performance monitoring and model-based operational planning in buildings. In this context, accurate indoor temperature models are vital to ensure that the proposed operational strategies are effective, feasible, and do not compromise indoor comfort. In this work, the significance of thermal space coupling for data-driven indoor temperature forecasting is investigated by assessing and comparing the performance of an isolated and coupled Long Short-Term Memory model architecture across 70 spaces in a case study building. To construct the coupled architecture, an open-source tool is developed and presented, which allows the automated extraction of space topology from IFC-files to identify adjacent spaces. The coupled architecture is found to outperform the isolated architecturefor ∼84% of the investigated spaces, with significant improvements under certain operational and climatic conditions. To account for the subset of spaces where the isolated architecture performs better, it is proposed to select between the two architectures accordingly. The demonstrated modularity and embedded adaptability of the proposed model architectures provide a sound basis for implementation in a highly dynamic building Digital Twin environment.},
68 | author = {Jakob Bj{\o}rnskov and Muhyiddine Jradi},
69 | year = {2022},
70 | month = {dec},
71 | language = {English},
72 | volume = {6},
73 | booktitle = {Proceedings of BSO Conference 2022: 6th Conference of IBPSA-England},
74 | publisher = {International Building Performance Simulation Association},
75 | note = {Building Simulation and Optimisation 2022 ; Conference date: 13-12-2022 Through 14-12-2022},
76 | }
77 | ```
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/__init__.py
--------------------------------------------------------------------------------
/adjacent_rooms_graph_test.dot:
--------------------------------------------------------------------------------
1 | strict graph {
2 | 002 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
3 | 001 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
4 | 015 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="3.6666666666666665", width="3.6666666666666665"];
5 | 101 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
6 | 102 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="2.333333333333333", width="2.333333333333333"];
7 | 003 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
8 | 103 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="2.333333333333333", width="2.333333333333333"];
9 | 004 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
10 | 104 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
11 | 005 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
12 | 016 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="2.0", width="2.0"];
13 | 117 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="3.333333333333333", width="3.333333333333333"];
14 | 006 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="3.333333333333333", width="3.333333333333333"];
15 | 017 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="2.0", width="2.0"];
16 | 105 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="2.333333333333333", width="2.333333333333333"];
17 | 118 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="4.0", width="4.0"];
18 | 110 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
19 | 106 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
20 | 109 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
21 | 108 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
22 | 107 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
23 | 007 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
24 | 111 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
25 | 008 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
26 | 112 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
27 | 009 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
28 | 010 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
29 | 114 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="3.0", width="3.0"];
30 | 011 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
31 | 012 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
32 | 013 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
33 | 115 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="2.0", width="2.0"];
34 | 014 [color="#808B96", fontname=Helvetica, fontsize="35.0", height="1.0", width="1.0"];
35 | 116 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="3.333333333333333", width="3.333333333333333"];
36 | 113 [color="#e99d4e", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
37 | 201 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
38 | 202 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="2.0", width="2.0"];
39 | 203 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="2.0", width="2.0"];
40 | 220 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="4.0", width="4.0"];
41 | 219 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
42 | 218 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
43 | 214 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
44 | 213 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
45 | 212 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
46 | 205 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
47 | 206 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="2.0", width="2.0"];
48 | 211 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
49 | 210 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
50 | 209 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
51 | 208 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
52 | 207 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
53 | 222 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="4.0", width="4.0"];
54 | 217 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
55 | 216 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
56 | 215 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
57 | 204 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
58 | 221 [color="#a6cee3", fontname=Helvetica, fontsize="35.0", height="2.333333333333333", width="2.333333333333333"];
59 | 320 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="4.0", width="4.0"];
60 | 314 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
61 | 313 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
62 | 322 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="4.0", width="4.0"];
63 | 311 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
64 | 321 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="2.333333333333333", width="2.333333333333333"];
65 | 306 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="2.0", width="2.0"];
66 | 307 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
67 | 305 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="2.0", width="2.0"];
68 | 310 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
69 | 319 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
70 | 318 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
71 | 317 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
72 | 315 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
73 | 304 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
74 | 301 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
75 | 308 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
76 | 302 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
77 | 303 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
78 | 309 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
79 | 312 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
80 | 316 [color="#b2df8a", fontname=Helvetica, fontsize="35.0", height="1.6666666666666665", width="1.6666666666666665"];
81 | 402 [color="#fddbd0", fontname=Helvetica, fontsize="35.0", height="3.6666666666666665", width="3.6666666666666665"];
82 | 403 [color="#fddbd0", fontname=Helvetica, fontsize="35.0", height="3.6666666666666665", width="3.6666666666666665"];
83 | 401 [color="#fddbd0", fontname=Helvetica, fontsize="35.0", height="1.3333333333333333", width="1.3333333333333333"];
84 | 002 -- 001 [color="#808B96", penwidth=5];
85 | 002 -- 003 [color="#808B96", penwidth=5];
86 | 002 -- 015 [color="#808B96", penwidth=5];
87 | 002 -- 102 [color="#91998e", penwidth=5];
88 | 002 -- 103 [color="#91998e", penwidth=5];
89 | 001 -- 015 [color="#808B96", penwidth=5];
90 | 001 -- 101 [color="#91998e", penwidth=5];
91 | 001 -- 102 [color="#91998e", penwidth=5];
92 | 015 -- 003 [color="#808B96", penwidth=5];
93 | 015 -- 010 [color="#808B96", penwidth=5];
94 | 015 -- 011 [color="#808B96", penwidth=5];
95 | 015 -- 012 [color="#808B96", penwidth=5];
96 | 015 -- 013 [color="#808B96", penwidth=5];
97 | 015 -- 014 [color="#808B96", penwidth=5];
98 | 015 -- 016 [color="#808B96", penwidth=5];
99 | 015 -- 009 [color="#808B96", penwidth=5];
100 | 015 -- 116 [color="#91998e", penwidth=5];
101 | 101 -- 102 [color="#e99d4e", penwidth=5];
102 | 101 -- 116 [color="#e99d4e", penwidth=5];
103 | 101 -- 201 [color="#91998e", penwidth=5];
104 | 102 -- 103 [color="#e99d4e", penwidth=5];
105 | 102 -- 116 [color="#e99d4e", penwidth=5];
106 | 102 -- 201 [color="#91998e", penwidth=5];
107 | 102 -- 202 [color="#91998e", penwidth=5];
108 | 003 -- 004 [color="#808B96", penwidth=5];
109 | 003 -- 103 [color="#91998e", penwidth=5];
110 | 003 -- 104 [color="#91998e", penwidth=5];
111 | 103 -- 104 [color="#e99d4e", penwidth=5];
112 | 103 -- 116 [color="#e99d4e", penwidth=5];
113 | 103 -- 202 [color="#91998e", penwidth=5];
114 | 103 -- 203 [color="#91998e", penwidth=5];
115 | 004 -- 005 [color="#808B96", penwidth=5];
116 | 004 -- 016 [color="#808B96", penwidth=5];
117 | 004 -- 117 [color="#91998e", penwidth=5];
118 | 104 -- 116 [color="#e99d4e", penwidth=5];
119 | 104 -- 117 [color="#e99d4e", penwidth=5];
120 | 104 -- 203 [color="#91998e", penwidth=5];
121 | 005 -- 006 [color="#808B96", penwidth=5];
122 | 005 -- 017 [color="#808B96", penwidth=5];
123 | 005 -- 105 [color="#91998e", penwidth=5];
124 | 016 -- 008 [color="#808B96", penwidth=5];
125 | 016 -- 017 [color="#808B96", penwidth=5];
126 | 016 -- 117 [color="#91998e", penwidth=5];
127 | 016 -- 009 [color="#808B96", penwidth=5];
128 | 117 -- 116 [color="#e99d4e", penwidth=5];
129 | 117 -- 113 [color="#e99d4e", penwidth=5];
130 | 117 -- 112 [color="#e99d4e", penwidth=5];
131 | 117 -- 105 [color="#e99d4e", penwidth=5];
132 | 117 -- 118 [color="#e99d4e", penwidth=5];
133 | 117 -- 204 [color="#91998e", penwidth=5];
134 | 117 -- 221 [color="#91998e", penwidth=5];
135 | 006 -- 105 [color="#91998e", penwidth=5];
136 | 006 -- 118 [color="#91998e", penwidth=5];
137 | 006 -- 110 [color="#91998e", penwidth=5];
138 | 006 -- 106 [color="#91998e", penwidth=5];
139 | 006 -- 109 [color="#91998e", penwidth=5];
140 | 006 -- 108 [color="#91998e", penwidth=5];
141 | 006 -- 107 [color="#91998e", penwidth=5];
142 | 006 -- 017 [color="#808B96", penwidth=5];
143 | 006 -- 007 [color="#808B96", penwidth=5];
144 | 017 -- 007 [color="#808B96", penwidth=5];
145 | 017 -- 008 [color="#808B96", penwidth=5];
146 | 017 -- 118 [color="#91998e", penwidth=5];
147 | 105 -- 106 [color="#e99d4e", penwidth=5];
148 | 105 -- 118 [color="#e99d4e", penwidth=5];
149 | 105 -- 205 [color="#91998e", penwidth=5];
150 | 105 -- 206 [color="#91998e", penwidth=5];
151 | 118 -- 112 [color="#e99d4e", penwidth=5];
152 | 118 -- 111 [color="#e99d4e", penwidth=5];
153 | 118 -- 110 [color="#e99d4e", penwidth=5];
154 | 118 -- 109 [color="#e99d4e", penwidth=5];
155 | 118 -- 108 [color="#e99d4e", penwidth=5];
156 | 118 -- 107 [color="#e99d4e", penwidth=5];
157 | 118 -- 106 [color="#e99d4e", penwidth=5];
158 | 118 -- 222 [color="#91998e", penwidth=5];
159 | 110 -- 111 [color="#e99d4e", penwidth=5];
160 | 110 -- 109 [color="#e99d4e", penwidth=5];
161 | 110 -- 211 [color="#91998e", penwidth=5];
162 | 106 -- 207 [color="#91998e", penwidth=5];
163 | 109 -- 108 [color="#e99d4e", penwidth=5];
164 | 109 -- 210 [color="#91998e", penwidth=5];
165 | 108 -- 107 [color="#e99d4e", penwidth=5];
166 | 108 -- 209 [color="#91998e", penwidth=5];
167 | 107 -- 208 [color="#91998e", penwidth=5];
168 | 007 -- 111 [color="#91998e", penwidth=5];
169 | 007 -- 008 [color="#808B96", penwidth=5];
170 | 111 -- 112 [color="#e99d4e", penwidth=5];
171 | 111 -- 212 [color="#91998e", penwidth=5];
172 | 008 -- 112 [color="#91998e", penwidth=5];
173 | 112 -- 213 [color="#91998e", penwidth=5];
174 | 009 -- 010 [color="#808B96", penwidth=5];
175 | 009 -- 113 [color="#91998e", penwidth=5];
176 | 010 -- 114 [color="#91998e", penwidth=5];
177 | 010 -- 011 [color="#808B96", penwidth=5];
178 | 114 -- 011 [color="#91998e", penwidth=5];
179 | 114 -- 012 [color="#91998e", penwidth=5];
180 | 114 -- 116 [color="#e99d4e", penwidth=5];
181 | 114 -- 115 [color="#e99d4e", penwidth=5];
182 | 114 -- 113 [color="#e99d4e", penwidth=5];
183 | 114 -- 217 [color="#91998e", penwidth=5];
184 | 114 -- 216 [color="#91998e", penwidth=5];
185 | 114 -- 215 [color="#91998e", penwidth=5];
186 | 011 -- 012 [color="#808B96", penwidth=5];
187 | 012 -- 013 [color="#808B96", penwidth=5];
188 | 013 -- 115 [color="#91998e", penwidth=5];
189 | 013 -- 014 [color="#808B96", penwidth=5];
190 | 115 -- 014 [color="#91998e", penwidth=5];
191 | 115 -- 116 [color="#e99d4e", penwidth=5];
192 | 115 -- 219 [color="#91998e", penwidth=5];
193 | 115 -- 218 [color="#91998e", penwidth=5];
194 | 116 -- 113 [color="#e99d4e", penwidth=5];
195 | 116 -- 220 [color="#91998e", penwidth=5];
196 | 113 -- 214 [color="#91998e", penwidth=5];
197 | 201 -- 220 [color="#a6cee3", penwidth=5];
198 | 201 -- 202 [color="#a6cee3", penwidth=5];
199 | 201 -- 301 [color="#91998e", penwidth=5];
200 | 202 -- 220 [color="#a6cee3", penwidth=5];
201 | 202 -- 203 [color="#a6cee3", penwidth=5];
202 | 202 -- 302 [color="#91998e", penwidth=5];
203 | 203 -- 220 [color="#a6cee3", penwidth=5];
204 | 203 -- 204 [color="#a6cee3", penwidth=5];
205 | 203 -- 303 [color="#91998e", penwidth=5];
206 | 220 -- 221 [color="#a6cee3", penwidth=5];
207 | 220 -- 219 [color="#a6cee3", penwidth=5];
208 | 220 -- 218 [color="#a6cee3", penwidth=5];
209 | 220 -- 217 [color="#a6cee3", penwidth=5];
210 | 220 -- 216 [color="#a6cee3", penwidth=5];
211 | 220 -- 215 [color="#a6cee3", penwidth=5];
212 | 220 -- 214 [color="#a6cee3", penwidth=5];
213 | 220 -- 320 [color="#91998e", penwidth=5];
214 | 219 -- 218 [color="#a6cee3", penwidth=5];
215 | 219 -- 319 [color="#91998e", penwidth=5];
216 | 218 -- 217 [color="#a6cee3", penwidth=5];
217 | 218 -- 318 [color="#91998e", penwidth=5];
218 | 214 -- 221 [color="#a6cee3", penwidth=5];
219 | 214 -- 314 [color="#91998e", penwidth=5];
220 | 214 -- 215 [color="#a6cee3", penwidth=5];
221 | 213 -- 212 [color="#a6cee3", penwidth=5];
222 | 213 -- 313 [color="#91998e", penwidth=5];
223 | 213 -- 221 [color="#a6cee3", penwidth=5];
224 | 213 -- 222 [color="#a6cee3", penwidth=5];
225 | 212 -- 222 [color="#a6cee3", penwidth=5];
226 | 212 -- 211 [color="#a6cee3", penwidth=5];
227 | 212 -- 312 [color="#91998e", penwidth=5];
228 | 205 -- 222 [color="#a6cee3", penwidth=5];
229 | 205 -- 206 [color="#a6cee3", penwidth=5];
230 | 205 -- 204 [color="#a6cee3", penwidth=5];
231 | 205 -- 305 [color="#91998e", penwidth=5];
232 | 206 -- 222 [color="#a6cee3", penwidth=5];
233 | 206 -- 207 [color="#a6cee3", penwidth=5];
234 | 206 -- 305 [color="#91998e", penwidth=5];
235 | 206 -- 306 [color="#91998e", penwidth=5];
236 | 211 -- 222 [color="#a6cee3", penwidth=5];
237 | 211 -- 210 [color="#a6cee3", penwidth=5];
238 | 211 -- 311 [color="#91998e", penwidth=5];
239 | 210 -- 222 [color="#a6cee3", penwidth=5];
240 | 210 -- 209 [color="#a6cee3", penwidth=5];
241 | 210 -- 310 [color="#91998e", penwidth=5];
242 | 209 -- 222 [color="#a6cee3", penwidth=5];
243 | 209 -- 208 [color="#a6cee3", penwidth=5];
244 | 209 -- 309 [color="#91998e", penwidth=5];
245 | 208 -- 222 [color="#a6cee3", penwidth=5];
246 | 208 -- 308 [color="#91998e", penwidth=5];
247 | 207 -- 222 [color="#a6cee3", penwidth=5];
248 | 207 -- 306 [color="#91998e", penwidth=5];
249 | 207 -- 307 [color="#91998e", penwidth=5];
250 | 222 -- 322 [color="#91998e", penwidth=5];
251 | 222 -- 221 [color="#a6cee3", penwidth=5];
252 | 217 -- 216 [color="#a6cee3", penwidth=5];
253 | 217 -- 317 [color="#91998e", penwidth=5];
254 | 216 -- 215 [color="#a6cee3", penwidth=5];
255 | 216 -- 316 [color="#91998e", penwidth=5];
256 | 215 -- 315 [color="#91998e", penwidth=5];
257 | 204 -- 221 [color="#a6cee3", penwidth=5];
258 | 204 -- 304 [color="#91998e", penwidth=5];
259 | 221 -- 321 [color="#91998e", penwidth=5];
260 | 320 -- 302 [color="#b2df8a", penwidth=5];
261 | 320 -- 303 [color="#b2df8a", penwidth=5];
262 | 320 -- 318 [color="#b2df8a", penwidth=5];
263 | 320 -- 317 [color="#b2df8a", penwidth=5];
264 | 320 -- 316 [color="#b2df8a", penwidth=5];
265 | 320 -- 315 [color="#b2df8a", penwidth=5];
266 | 320 -- 314 [color="#b2df8a", penwidth=5];
267 | 320 -- 301 [color="#b2df8a", penwidth=5];
268 | 320 -- 319 [color="#b2df8a", penwidth=5];
269 | 320 -- 321 [color="#b2df8a", penwidth=5];
270 | 320 -- 403 [color="#91998e", penwidth=5];
271 | 314 -- 315 [color="#b2df8a", penwidth=5];
272 | 314 -- 321 [color="#b2df8a", penwidth=5];
273 | 314 -- 403 [color="#91998e", penwidth=5];
274 | 313 -- 312 [color="#b2df8a", penwidth=5];
275 | 313 -- 402 [color="#91998e", penwidth=5];
276 | 313 -- 321 [color="#b2df8a", penwidth=5];
277 | 313 -- 322 [color="#b2df8a", penwidth=5];
278 | 322 -- 305 [color="#b2df8a", penwidth=5];
279 | 322 -- 306 [color="#b2df8a", penwidth=5];
280 | 322 -- 307 [color="#b2df8a", penwidth=5];
281 | 322 -- 312 [color="#b2df8a", penwidth=5];
282 | 322 -- 311 [color="#b2df8a", penwidth=5];
283 | 322 -- 310 [color="#b2df8a", penwidth=5];
284 | 322 -- 309 [color="#b2df8a", penwidth=5];
285 | 322 -- 308 [color="#b2df8a", penwidth=5];
286 | 322 -- 321 [color="#b2df8a", penwidth=5];
287 | 322 -- 402 [color="#91998e", penwidth=5];
288 | 311 -- 312 [color="#b2df8a", penwidth=5];
289 | 311 -- 310 [color="#b2df8a", penwidth=5];
290 | 311 -- 402 [color="#91998e", penwidth=5];
291 | 321 -- 304 [color="#b2df8a", penwidth=5];
292 | 321 -- 401 [color="#91998e", penwidth=5];
293 | 306 -- 305 [color="#b2df8a", penwidth=5];
294 | 306 -- 307 [color="#b2df8a", penwidth=5];
295 | 306 -- 402 [color="#91998e", penwidth=5];
296 | 307 -- 402 [color="#91998e", penwidth=5];
297 | 305 -- 304 [color="#b2df8a", penwidth=5];
298 | 305 -- 402 [color="#91998e", penwidth=5];
299 | 310 -- 309 [color="#b2df8a", penwidth=5];
300 | 310 -- 402 [color="#91998e", penwidth=5];
301 | 319 -- 318 [color="#b2df8a", penwidth=5];
302 | 319 -- 403 [color="#91998e", penwidth=5];
303 | 318 -- 317 [color="#b2df8a", penwidth=5];
304 | 318 -- 403 [color="#91998e", penwidth=5];
305 | 317 -- 316 [color="#b2df8a", penwidth=5];
306 | 317 -- 403 [color="#91998e", penwidth=5];
307 | 315 -- 316 [color="#b2df8a", penwidth=5];
308 | 315 -- 403 [color="#91998e", penwidth=5];
309 | 304 -- 303 [color="#b2df8a", penwidth=5];
310 | 304 -- 401 [color="#91998e", penwidth=5];
311 | 301 -- 302 [color="#b2df8a", penwidth=5];
312 | 301 -- 403 [color="#91998e", penwidth=5];
313 | 308 -- 309 [color="#b2df8a", penwidth=5];
314 | 308 -- 402 [color="#91998e", penwidth=5];
315 | 302 -- 303 [color="#b2df8a", penwidth=5];
316 | 302 -- 403 [color="#91998e", penwidth=5];
317 | 303 -- 403 [color="#91998e", penwidth=5];
318 | 309 -- 402 [color="#91998e", penwidth=5];
319 | 312 -- 402 [color="#91998e", penwidth=5];
320 | 316 -- 403 [color="#91998e", penwidth=5];
321 | 402 -- 401 [color="#fddbd0", penwidth=5];
322 | 403 -- 401 [color="#fddbd0", penwidth=5];
323 | }
324 |
--------------------------------------------------------------------------------
/adjacent_rooms_graph_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/adjacent_rooms_graph_test.png
--------------------------------------------------------------------------------
/ifc2graph/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/__init__.py
--------------------------------------------------------------------------------
/ifc2graph/__pycache__/IfcGeometry.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/__pycache__/IfcGeometry.cpython-37.pyc
--------------------------------------------------------------------------------
/ifc2graph/__pycache__/SpaceGeometryContainer.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/__pycache__/SpaceGeometryContainer.cpython-37.pyc
--------------------------------------------------------------------------------
/ifc2graph/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/ifc2graph/__pycache__/extract_adjacent_rooms_from_ifc.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/__pycache__/extract_adjacent_rooms_from_ifc.cpython-37.pyc
--------------------------------------------------------------------------------
/ifc2graph/__pycache__/extract_geometry_from_ifc.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/__pycache__/extract_geometry_from_ifc.cpython-37.pyc
--------------------------------------------------------------------------------
/ifc2graph/__pycache__/graph.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/__pycache__/graph.cpython-37.pyc
--------------------------------------------------------------------------------
/ifc2graph/__pycache__/ifcgraph.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/__pycache__/ifcgraph.cpython-37.pyc
--------------------------------------------------------------------------------
/ifc2graph/ifcgeometry.py:
--------------------------------------------------------------------------------
1 | #misc non-standard libraries
2 | import ifcopenshell
3 | import ifcopenshell.geom
4 | import trimesh
5 |
6 | #custom
7 | import ifc2graph.utils.progressbar as progressbar
8 |
9 | #standard
10 | import math
11 | import os
12 | import numpy as np
13 | import itertools
14 |
15 |
16 |
17 |
18 |
19 | class IfcGeometry:
20 | def __init__(self,
21 | ifc_file_path=None, #An open ifc-file
22 | name = None, #Name of the SpaceGeometryContainer
23 | force_init=False, #Force the creation of a new SpaceGeometryContainer object
24 | exclude_space_list=[], #List with names of rooms that should not be included
25 | voxel_distance=0.5): #Distance between grid voxels along each axis X, Y, Z
26 |
27 | self.ifc_file_path = ifc_file_path
28 | self.name = name
29 | self.force_init = force_init
30 | self.exclude_space_list = exclude_space_list
31 | self.voxel_distance = voxel_distance
32 | self.x_size = None
33 | self.y_size = None
34 | self.z_size = None
35 | self.voxel_x_location_vec = None
36 | self.voxel_y_location_vec = None
37 | self.voxel_z_location_vec = None
38 | self.grid_shape = None
39 | self.space_idx_dict = {}
40 | self.neutral_idx = -50
41 | self.ambient_idx = -100
42 |
43 | # This is a color list for the building stories
44 | self.color_list = ["#808B96", "#e99d4e", "#a6cee3", "#b2df8a", "#fddbd0", "#91998e"] #Last is default
45 |
46 | self.init_geometry()
47 | self.init_3D_space_idx_array()
48 |
49 | def init_geometry(self):
50 | current_dir = os.path.dirname(os.path.realpath(__file__))
51 | space_map_filename = current_dir + "/" + "space_map_" + self.name + ".pickle"
52 | if self.force_init or os.path.isfile(space_map_filename) == False:
53 | settings = ifcopenshell.geom.settings()
54 | settings.set(settings.USE_WORLD_COORDS, True)
55 | id_iter = itertools.count()
56 |
57 | print("Extracting geometry from ifc...")
58 | neutral_space_name = "Area" #spaces that doesnt classify as a room but is a part of the building
59 | ifc_file = ifcopenshell.open(self.ifc_file_path)
60 | ifc_space_list = ifc_file.by_type("IfcSpace")
61 | ifc_storey_list = ifc_file.by_type("IfcBuildingStorey")
62 |
63 | if len(ifc_space_list)==0:
64 | print("Ifc-file does not contain any space objects -> quitting...")
65 | else:
66 | self.space_name_list = []
67 | self.space_mesh_list = []
68 | self.space_type_name_dict = {}
69 | self.space_storey_dict = {}
70 |
71 | self.space_name_neutral_list = []
72 | self.space_mesh_neutral_list = []
73 |
74 | space_counter = 0
75 | for storey_counter,storey in enumerate(ifc_storey_list):
76 | for ifc_rel_aggregates in storey.IsDecomposedBy:
77 | for space in ifc_rel_aggregates.RelatedObjects:
78 | shape = ifcopenshell.geom.create_shape(settings, space)
79 | vertices = shape.geometry.verts
80 | edges = shape.geometry.edges
81 | faces = shape.geometry.faces
82 |
83 | if space.Name is None: #If the space has no name, give it a generic name
84 | space.Name = "Space_" + str(next(id_iter))
85 |
86 | grouped_vertices = np.array([[vertices[i], vertices[i + 1], vertices[i + 2]] for i in range(0, len(vertices), 3)])
87 | grouped_edges = np.array([[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)])
88 | grouped_faces = np.array([[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)])
89 |
90 | mesh = trimesh.Trimesh(vertices=grouped_vertices,
91 | faces=grouped_faces)
92 |
93 | if (space.Name in self.exclude_space_list)==False:
94 | if space.LongName == neutral_space_name:
95 | self.space_name_neutral_list.append(space.Name)
96 | self.space_mesh_neutral_list.append(mesh)
97 | else:
98 | self.space_name_list.append(space.Name)
99 | self.space_mesh_list.append(mesh)
100 | self.space_type_name_dict[space.Name] = space.LongName
101 | self.space_storey_dict[space.Name] = storey_counter
102 |
103 | progressbar.progressbar(space_counter,0,len(ifc_space_list)-1)
104 | space_counter += 1
105 |
106 | #Check for duplicate room names
107 | if len(set(self.space_name_list)) != len(self.space_name_list):
108 | print("Warning: duplicate space names found in IFC.")
109 |
110 | def init_3D_space_idx_array(self):
111 | min_x_list = []
112 | min_y_list = []
113 | min_z_list = []
114 |
115 | max_x_list = []
116 | max_y_list = []
117 | max_z_list = []
118 |
119 | for space_mesh in self.space_mesh_list:
120 | min_x_list.append(np.min(space_mesh.vertices[:,0]))
121 | min_y_list.append(np.min(space_mesh.vertices[:,1]))
122 | min_z_list.append(np.min(space_mesh.vertices[:,2]))
123 |
124 | max_x_list.append(np.max(space_mesh.vertices[:,0]))
125 | max_y_list.append(np.max(space_mesh.vertices[:,1]))
126 | max_z_list.append(np.max(space_mesh.vertices[:,2]))
127 |
128 | for space_mesh in self.space_mesh_neutral_list:
129 | min_x_list.append(np.min(space_mesh.vertices[:,0]))
130 | min_y_list.append(np.min(space_mesh.vertices[:,1]))
131 | min_z_list.append(np.min(space_mesh.vertices[:,2]))
132 |
133 | max_x_list.append(np.max(space_mesh.vertices[:,0]))
134 | max_y_list.append(np.max(space_mesh.vertices[:,1]))
135 | max_z_list.append(np.max(space_mesh.vertices[:,2]))
136 |
137 | min_x = np.min(np.array(min_x_list))
138 | min_y = np.min(np.array(min_y_list))
139 | min_z = np.min(np.array(min_z_list))
140 |
141 | max_x = np.max(np.array(max_x_list))
142 | max_y = np.max(np.array(max_y_list))
143 | max_z = np.max(np.array(max_z_list))
144 |
145 | self.x_size = math.floor((max_x-min_x)/self.voxel_distance)+2
146 | self.y_size = math.floor((max_y-min_y)/self.voxel_distance)+2
147 | self.z_size = math.floor((max_z-min_z)/self.voxel_distance)+2
148 |
149 | x_rem = (max_x-min_x)-self.x_size*self.voxel_distance
150 | z_rem = (max_y-min_y)-self.y_size*self.voxel_distance
151 | y_rem = (max_z-min_z)-self.z_size*self.voxel_distance
152 |
153 | min_x = min_x+x_rem*0.5 - self.voxel_distance #add additional outdoor block
154 | min_y = min_y+y_rem*0.5 - self.voxel_distance #add additional outdoor block
155 | min_z = min_z+z_rem*0.5 - self.voxel_distance #add additional outdoor block
156 |
157 | max_x = max_x-x_rem*0.5 + self.voxel_distance #add additional outdoor block
158 | max_y = max_y-y_rem*0.5 + self.voxel_distance #add additional outdoor block
159 | max_z = max_z-z_rem*0.5 + self.voxel_distance #add additional outdoor block
160 |
161 | voxel_x_idx_vec = np.arange(self.x_size) #(x_size)
162 | voxel_y_idx_vec = np.arange(self.y_size) #(y_size)
163 | voxel_z_idx_vec = np.arange(self.z_size) #(z_size)
164 |
165 | self.voxel_x_location_vec = np.linspace(min_x,max_x,self.x_size) #(x_size)
166 | self.voxel_y_location_vec = np.linspace(min_y,max_y,self.y_size) #(y_size)
167 | self.voxel_z_location_vec = np.linspace(min_z,max_z,self.z_size) #(conv_z_size)
168 |
169 | voxel_x_idx_mesh,voxel_y_idx_mesh,voxel_z_idx_mesh = np.meshgrid(voxel_x_idx_vec,voxel_y_idx_vec,voxel_z_idx_vec)
170 | voxel_x_location_mesh,voxel_y_location_mesh,voxel_z_location_mesh = np.meshgrid(self.voxel_x_location_vec,self.voxel_y_location_vec,self.voxel_z_location_vec)
171 |
172 | idx_mesh = np.swapaxes(np.array([voxel_x_idx_mesh,voxel_y_idx_mesh,voxel_z_idx_mesh]),0,3) #(conv_x_size,conv_y_size,conv_z_size,3)
173 | location_mesh = np.swapaxes(np.array([voxel_x_location_mesh,voxel_y_location_mesh,voxel_z_location_mesh]),0,3) #(conv_x_size,conv_y_size,conv_z_size)
174 |
175 | idx_mesh_vec = np.reshape(idx_mesh,(idx_mesh.shape[0]*idx_mesh.shape[1]*idx_mesh.shape[2],idx_mesh.shape[3]))
176 | location_mesh_vec = np.reshape(location_mesh,(location_mesh.shape[0]*location_mesh.shape[1]*location_mesh.shape[2],location_mesh.shape[3]))
177 |
178 | print("Generating space to position index map...")
179 | self.grid_shape = (self.x_size,self.y_size,self.z_size)
180 | self._3D_space_idx_array = np.ones(self.grid_shape,dtype=np.int)*self.ambient_idx
181 |
182 | #Set indices for neutral space
183 | for space_counter,space_mesh in enumerate(self.space_mesh_neutral_list):
184 | bool_vec = space_mesh.contains(location_mesh_vec)
185 | x_idx = idx_mesh_vec[bool_vec,0]
186 | y_idx = idx_mesh_vec[bool_vec,1]
187 | z_idx = idx_mesh_vec[bool_vec,2]
188 | self._3D_space_idx_array[x_idx,y_idx,z_idx] = self.neutral_idx
189 |
190 | progressbar.progressbar(space_counter,0,len(self.space_mesh_neutral_list)-1)
191 |
192 | #Set indices for normal space
193 | for space_counter,space_mesh in enumerate(self.space_mesh_list):
194 | bool_vec = space_mesh.contains(location_mesh_vec)
195 | x_idx = idx_mesh_vec[bool_vec,0]
196 | y_idx = idx_mesh_vec[bool_vec,1]
197 | z_idx = idx_mesh_vec[bool_vec,2]
198 | self._3D_space_idx_array[x_idx,y_idx,z_idx] = space_counter
199 | self.space_idx_dict[self.space_name_list[space_counter]] = space_counter
200 |
201 | progressbar.progressbar(space_counter,0,len(self.space_mesh_list)-1)
202 |
203 | def get_point_space_idx(self,x_idx,y_idx,z_idx):
204 | return self._3D_space_idx_array[x_idx,y_idx,z_idx]
205 |
206 | def get_adjacent_spaces_dict(self):
207 | x_bool = np.equal(self._3D_space_idx_array[:-1,:,:], self._3D_space_idx_array[1:,:,:]) == False
208 | y_bool = (self._3D_space_idx_array[:,:-1,:] == self._3D_space_idx_array[:,1:,:]) == False
209 | z_bool = (self._3D_space_idx_array[:,:,:-1] == self._3D_space_idx_array[:,:,1:]) == False
210 |
211 | dx_idx_space_1_x, dx_idx_space_1_y, dx_idx_space_1_z = np.where(x_bool)
212 | dy_idx_space_1_x, dy_idx_space_1_y, dy_idx_space_1_z = np.where(y_bool)
213 | dz_idx_space_1_x, dz_idx_space_1_y, dz_idx_space_1_z = np.where(z_bool)
214 |
215 | dx_idx_space_2_x = dx_idx_space_1_x+1
216 | dy_idx_space_2_x = dy_idx_space_1_x
217 | dz_idx_space_2_x = dz_idx_space_1_x
218 |
219 | dx_idx_space_2_y = dx_idx_space_1_y
220 | dy_idx_space_2_y = dy_idx_space_1_y+1
221 | dz_idx_space_2_y = dz_idx_space_1_y
222 |
223 | dx_idx_space_2_z = dx_idx_space_1_z
224 | dy_idx_space_2_z = dy_idx_space_1_z
225 | dz_idx_space_2_z = dz_idx_space_1_z+1
226 |
227 | dx_idx_pair_1 = self._3D_space_idx_array[dx_idx_space_1_x, dx_idx_space_1_y, dx_idx_space_1_z]
228 | dx_idx_pair_2 = self._3D_space_idx_array[dx_idx_space_2_x, dx_idx_space_2_y, dx_idx_space_2_z]
229 | dy_idx_pair_1 = self._3D_space_idx_array[dy_idx_space_1_x, dy_idx_space_1_y, dy_idx_space_1_z]
230 | dy_idx_pair_2 = self._3D_space_idx_array[dy_idx_space_2_x, dy_idx_space_2_y, dy_idx_space_2_z]
231 | dz_idx_pair_1 = self._3D_space_idx_array[dz_idx_space_1_x, dz_idx_space_1_y, dz_idx_space_1_z]
232 | dz_idx_pair_2 = self._3D_space_idx_array[dz_idx_space_2_x, dz_idx_space_2_y, dz_idx_space_2_z]
233 |
234 | # For each point in the generated mesh, it is checked which space the adjacent point is encapsulated by.
235 | # If the adjacent point does not belong to a space, the subsequent point is checked.
236 | # This is done up until "n_search_blocks" away from the original point.
237 | n_search_blocks = 4
238 | for i in range(n_search_blocks):
239 | dx_temp = dx_idx_space_2_x+i+1
240 | dx_temp[dx_temp>=self.grid_shape[0]] = dx_idx_space_2_x[dx_temp>=self.grid_shape[0]]
241 | temp = self._3D_space_idx_array[dx_temp, dx_idx_space_2_y, dx_idx_space_2_z] ### If +1 point is not a room then check +2 point
242 | bool_dx = np.logical_or(dx_idx_pair_2 == self.neutral_idx, dx_idx_pair_2 == self.ambient_idx) ###
243 | dx_idx_pair_2[bool_dx] = temp[bool_dx] ###
244 |
245 | dy_temp = dy_idx_space_2_y+i+1
246 | dy_temp[dy_temp>=self.grid_shape[1]] = dy_idx_space_2_y[dy_temp>=self.grid_shape[1]]
247 | temp = self._3D_space_idx_array[dy_idx_space_2_x, dy_temp, dy_idx_space_2_z] ###
248 | bool_dy = np.logical_or(dy_idx_pair_2 == self.neutral_idx, dy_idx_pair_2 == self.ambient_idx) ###
249 | dy_idx_pair_2[bool_dy] = temp[bool_dy] ###
250 |
251 | dz_temp = dz_idx_space_2_z+i+1
252 | dz_temp[dz_temp>=self.grid_shape[2]] = dz_idx_space_2_z[dz_temp>=self.grid_shape[2]]
253 | temp = self._3D_space_idx_array[dz_idx_space_2_x, dz_idx_space_2_y, dz_temp] ###
254 | bool_dz = np.logical_or(dz_idx_pair_2 == self.neutral_idx, dz_idx_pair_2 == self.ambient_idx) ###
255 | dz_idx_pair_2[bool_dz] = temp[bool_dz] ###
256 |
257 | dx_idx_pair = np.array([dx_idx_pair_1,dx_idx_pair_2])
258 | dy_idx_pair = np.array([dy_idx_pair_1,dy_idx_pair_2])
259 | dz_idx_pair = np.array([dz_idx_pair_1,dz_idx_pair_2])
260 |
261 | dx_pair_list = []
262 | while True:
263 | dx_pair_list.append(dx_idx_pair[:,0])
264 | is_equal_bool = np.equal(dx_idx_pair[:,0].reshape((2,1)), dx_idx_pair)
265 | is_equal_bool = np.logical_and(is_equal_bool[0],is_equal_bool[1])
266 | if np.all(is_equal_bool):
267 | break
268 | else:
269 | dx_idx_pair = dx_idx_pair[:,is_equal_bool==False]
270 |
271 | dy_pair_list = []
272 | while True:
273 | dy_pair_list.append(dy_idx_pair[:,0])
274 | is_equal_bool = np.equal(dy_idx_pair[:,0].reshape((2,1)), dy_idx_pair)
275 | is_equal_bool = np.logical_and(is_equal_bool[0],is_equal_bool[1])
276 | if np.all(is_equal_bool):
277 | break
278 | else:
279 | dy_idx_pair = dy_idx_pair[:,is_equal_bool==False]
280 |
281 | dz_pair_list = []
282 | while True:
283 | dz_pair_list.append(dz_idx_pair[:,0])
284 | is_equal_bool = np.equal(dz_idx_pair[:,0].reshape((2,1)), dz_idx_pair)
285 | is_equal_bool = np.logical_and(is_equal_bool[0],is_equal_bool[1])
286 | if np.all(is_equal_bool):
287 | break
288 | else:
289 | dz_idx_pair = dz_idx_pair[:,is_equal_bool==False]
290 |
291 | pair_list = []
292 | pair_list.extend(dx_pair_list)
293 | pair_list.extend(dy_pair_list)
294 | pair_list.extend(dz_pair_list)
295 | pair_vec = np.array(pair_list).transpose()
296 | bool_no_self_reference = pair_vec[0,:] != pair_vec[1,:]
297 | pair_vec = pair_vec[:,bool_no_self_reference]
298 | pair_list_no_duplicates = []
299 | while True:
300 | pair_1 = pair_vec[:,0]
301 | pair_2 = np.array([pair_vec[1,0],pair_vec[0,0]])
302 | pair_list_no_duplicates.append(pair_1)
303 | is_equal_bool_1 = np.equal(pair_1.reshape((2,1)), pair_vec)
304 | is_equal_bool_1 = np.logical_and(is_equal_bool_1[0],is_equal_bool_1[1])
305 | is_equal_bool_2 = np.equal(pair_2.reshape((2,1)), pair_vec)
306 | is_equal_bool_2 = np.logical_and(is_equal_bool_2[0],is_equal_bool_2[1])
307 | is_equal_bool = np.logical_or(is_equal_bool_1, is_equal_bool_2)
308 | if np.all(is_equal_bool):
309 | break
310 | else:
311 | pair_vec = pair_vec[:,is_equal_bool==False]
312 |
313 | adjacent_spaces_dict = {}
314 | pair_vec_no_duplicates = np.array(pair_list_no_duplicates).transpose()
315 | for space_counter,space_name in enumerate(self.space_name_list):
316 | bool_1 = space_counter == pair_vec_no_duplicates[0,:]
317 | bool_2 = space_counter == pair_vec_no_duplicates[1,:]
318 | idx_vec_1 = pair_vec_no_duplicates[1,bool_1]
319 | idx_vec_2 = pair_vec_no_duplicates[0,bool_2]
320 | adjacent_space_list = []
321 | for el in idx_vec_1:
322 | if el != -50 and el != -100:
323 | adjacent_space_list.append(self.space_name_list[el])
324 | elif el == -100:
325 | adjacent_space_list.append("Outside")
326 | for el in idx_vec_2:
327 | if el != -50 and el != -100:
328 | adjacent_space_list.append(self.space_name_list[el])
329 | elif el == -100:
330 | adjacent_space_list.append("Outside")
331 | adjacent_spaces_dict[space_name] = adjacent_space_list
332 |
333 | ####### IF duplicate space names occur in the IFC and these spaces are adjacent then the resulting adjacent_space_dict will have elements that reference themselves #########
334 |
335 | return adjacent_spaces_dict
336 |
337 | def visualize(self):
338 | scene = trimesh.Scene()
339 | point_cloud = []
340 | for space_name,mesh in zip(self.space_name_list,self.space_mesh_list):
341 | mesh.visual.face_colors = trimesh.visual.color.hex_to_rgba(self.color_list[self.space_storey_dict[space_name]])
342 | point_cloud.extend(list(mesh.vertices))
343 | scene.add_geometry(mesh)
344 |
345 | angles = np.array([0.4*np.pi,0,-0.25*np.pi])
346 | scene.set_camera(angles=angles)
347 | scene.show()
--------------------------------------------------------------------------------
/ifc2graph/ifcgraph.py:
--------------------------------------------------------------------------------
1 | import networkx as nx
2 | import os
3 | import shutil
4 | import subprocess
5 |
6 |
7 | class IfcGraph(nx.Graph):
8 |
9 | def __init__(self,
10 | adjacent_spaces_dict=None,
11 | space_type_name_dict=None,
12 | name_to_type_dict=None,
13 | space_storey_dict = None,
14 | graph_name=None):
15 | self.adjacent_spaces_dict = adjacent_spaces_dict
16 | self.space_type_name_dict = space_type_name_dict
17 | self.name_to_type_dict = name_to_type_dict
18 | self.space_storey_dict = space_storey_dict
19 | self.graph_name = graph_name
20 |
21 | # This is a color list for the building stories
22 | self.color_list = ["#808B96", "#e99d4e", "#a6cee3", "#b2df8a", "#fddbd0", "#91998e"] #Last is default
23 | super().__init__()
24 |
25 | def generate(self, save_dir=None):
26 | graph_node_attribute_dict = {}
27 |
28 | for space_name,adjacent_spaces_list in self.adjacent_spaces_dict.items():
29 | story_space_idx = self.space_storey_dict[space_name]
30 | for adjacent_space in adjacent_spaces_list:
31 | if adjacent_space is not "Outside":
32 | story_adjacent_idx = self.space_storey_dict[adjacent_space]
33 | if story_space_idx!=story_adjacent_idx:
34 | story_idx = len(self.color_list)-1
35 | else:
36 | story_idx = story_space_idx
37 |
38 | self.add_edge(adjacent_space, space_name, color=self.color_list[story_idx])
39 | if self.space_type_name_dict is not None and self.name_to_type_dict is not None:
40 | if self.space_type_name_dict[adjacent_space] in self.name_to_type_dict:
41 | graph_node_attribute_dict[adjacent_space] = {"label": self.name_to_type_dict[self.space_type_name_dict[adjacent_space]]}
42 | else:
43 | graph_node_attribute_dict[adjacent_space] = {"label": self.space_type_name_dict[adjacent_space]}
44 |
45 | if self.space_type_name_dict[space_name] in self.name_to_type_dict:
46 | graph_node_attribute_dict[space_name] = {"label": self.name_to_type_dict[self.space_type_name_dict[space_name]]}
47 | else:
48 | graph_node_attribute_dict[space_name] = {"label": self.space_type_name_dict[space_name]}
49 | min_fontsize = 35
50 | max_fontsize = 35
51 |
52 | min_width = 1
53 | max_width = 4
54 |
55 | degree_list = [self.degree(node) for node in self.nodes]
56 | min_deg = min(degree_list)
57 | max_deg = max(degree_list)
58 |
59 | a_fontsize = (max_fontsize-min_fontsize)/(max_deg-min_deg)
60 | b_fontsize = max_fontsize-a_fontsize*max_deg
61 |
62 | a_width = (max_width-min_width)/(max_deg-min_deg)
63 | b_width = max_width-a_width*max_deg
64 | for node in self.nodes:
65 | deg = self.degree(node)
66 | fontsize = a_fontsize*deg + b_fontsize
67 | width = a_width*deg + b_width
68 |
69 | story_idx = self.space_storey_dict[node]
70 |
71 |
72 | if node not in graph_node_attribute_dict:
73 | graph_node_attribute_dict[node] = {"fontsize":fontsize, "width":width, "height":width, "color":self.color_list[story_idx]}
74 | else:
75 | graph_node_attribute_dict[node]["fontsize"] = fontsize
76 | graph_node_attribute_dict[node]["width"] = width
77 | graph_node_attribute_dict[node]["height"] = width
78 | graph_node_attribute_dict[node]["color"] = self.color_list[story_idx]
79 |
80 |
81 | nx.set_node_attributes(self, graph_node_attribute_dict)
82 | nx.set_node_attributes(self, values="Helvetica", name="fontname")
83 | nx.set_edge_attributes(self, values=5, name="penwidth")
84 | # nx.set_node_attributes(self, values=10, name="fontsize")
85 |
86 |
87 | print("Drawing graph...")
88 | if save_dir is None:
89 | file_name = "adjacent_rooms_graph_" + self.graph_name
90 | else:
91 | file_name = os.path.join(save_dir, "adjacent_rooms_graph_" + self.graph_name)
92 |
93 |
94 | nx.drawing.nx_pydot.write_dot(self, file_name+".dot")
95 |
96 | # If Python can't find the dot executeable, change "app_path" variable to the full path
97 | app_path = shutil.which("dot")
98 | args = [app_path,
99 | "-Tpng",
100 | "-Ksfdp",
101 | "-Nstyle=filled",
102 | "-Nfixedsize=true",
103 | "-Grankdir=LR",
104 | "-Goverlap=scale",
105 | "-Gsplines=true",
106 | "-Gmargin=0",
107 | "-Gratio=fill",
108 | "-Gsize=7,5!",
109 | "-Gpack=true",
110 | "-Gdpi=1000",
111 | "-Grepulsiveforce=10",
112 | "-o" + file_name + ".png",
113 | file_name + ".dot"]
114 |
115 | subprocess.run(args=args)
116 |
117 | cwd = os.getcwd()
118 | print("Generated graph can be found in directory: \"" + cwd + "\"")
119 |
--------------------------------------------------------------------------------
/ifc2graph/utils/Timer.py:
--------------------------------------------------------------------------------
1 | import math
2 | import time
3 | class Timer:
4 | def __init__(self,checkpoint_name):
5 | self.start_time = time.time()
6 | self.checkpoint_name = checkpoint_name
7 |
8 | def print_elapsed_time(self):
9 | total_elapsed_secs = time.time()-self.start_time
10 | h = math.floor(total_elapsed_secs/3600)
11 | m = math.floor((total_elapsed_secs-h*3600)/60)
12 | s = math.floor((total_elapsed_secs-h*3600-m*60))
13 | str1 = "\"%s\" elapsed time: %dh %dm %ds" % (self.checkpoint_name,h,m,s)
14 | print(str1)
--------------------------------------------------------------------------------
/ifc2graph/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/utils/__init__.py
--------------------------------------------------------------------------------
/ifc2graph/utils/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/utils/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/ifc2graph/utils/__pycache__/progressbar.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JBjoernskov/Ifc2Graph/872622b6b147a86028047991e3e65c803732b3c3/ifc2graph/utils/__pycache__/progressbar.cpython-37.pyc
--------------------------------------------------------------------------------
/ifc2graph/utils/progressbar.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import math
3 |
4 | def progressbar(current,start,stop):
5 | total_time = stop-start
6 | relative_time = current-start
7 |
8 | n_ticks_total = 40
9 | n_ticks_current = math.ceil(relative_time/total_time*n_ticks_total)
10 | percent_done = math.ceil(relative_time/total_time*100)
11 |
12 | progress_str = '|'
13 | for i in range(n_ticks_total):
14 | if n_ticks_current > i:
15 | progress_str += '#'
16 | else:
17 | progress_str += '-'
18 |
19 | progress_str += '| ' + str(percent_done) + '% '
20 |
21 |
22 | sys.stdout.write('\r\x1b[K' + progress_str)
23 | sys.stdout.flush()
24 |
25 | if current==stop:
26 | print("")
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | setup(
4 | name="ifc2graph",
5 | python_requires='>=3.7',
6 | version="0.0.0",
7 | description="A tool designed to extract room topology from files following the Industry Foundation Classes (IFC) standard.",
8 | url="https://github.com/JBjoernskov/Ifc2Graph",
9 | keywords="ifc, graph, room topology",
10 | author="Jakob Bjørnskov, Center for Energy Informatics SDU",
11 | author_email="jakob.bjornskov@me.com, jabj@mmmi.sdu.dk",
12 | license="BSD",
13 | platforms=["Windows", "Linux"],
14 | packages=[
15 | "ifc2graph",
16 | "ifc2graph.utils",
17 | ],
18 | include_package_data=True,
19 | install_requires=[
20 | "ifcopenshell",
21 | "Trimesh",
22 | "rtree", # Used by Trimesh
23 | "scipy", # Used by Trimesh
24 | "pyglet", # Used by Trimesh
25 | "NumPy",
26 | "NetworkX",
27 | "pydot" # Used by NetworkX
28 | ],
29 | classifiers=["Programming Language :: Python :: 3"],
30 | )
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import os
3 | import ifc2graph.ifcgeometry as ifcgeometry
4 | import ifc2graph.ifcgraph as ifcgraph
5 |
6 | path = str(Path(__file__).parent)
7 | project_name = "test"
8 | ifc_file_name = "Office Building.ifc"
9 | # ifc_file_name = "Residential House.ifc"
10 | ifc_file_path = os.path.join(path, "test_ifc_files", ifc_file_name)
11 |
12 | # First the IfcGeometry object is instantiated.
13 | ifc_geometry = ifcgeometry.IfcGeometry(ifc_file_path, project_name, force_init=False)
14 |
15 | # The extracted geometry can be visualized using the below function (this will open a new window).
16 | # Simply close the opened window to execute the rest of this file.
17 | ifc_geometry.visualize()
18 |
19 | # Using the IfcGeometry object, a dictionary can be created.
20 | # Here, the keys corresponds to the Name attribute of the IfcSpace entities contained in the IFC input file.
21 | # The values of this dictionary are lists containing the Name attributes of the adjacent IfcSpace entities.
22 | adjacent_spaces_dict = ifc_geometry.get_adjacent_spaces_dict()
23 |
24 | # To produce an image of the generated graph, a Graph object must be instantiated.
25 | ifc_graph = ifcgraph.IfcGraph(adjacent_spaces_dict=adjacent_spaces_dict,
26 | space_type_name_dict=ifc_geometry.space_type_name_dict,
27 | space_storey_dict=ifc_geometry.space_storey_dict,
28 | graph_name=project_name)
29 |
30 |
31 | # This function saves by default an image of the generated graph in the current working directory.
32 | # Optionally, a save-path can be given as argument.
33 | # E.g.:
34 | # ifc_graph.generate(save_dir="path/to/save")
35 | ifc_graph.generate()
36 |
--------------------------------------------------------------------------------