├── 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 | --------------------------------------------------------------------------------