├── .gitmodules ├── CMakeLists.txt ├── README.md ├── context.h ├── geobim.cpp ├── global_execution_context.cpp ├── global_execution_context.h ├── opening_collector.cpp ├── opening_collector.h ├── processing.cpp ├── processing.h ├── radius_comparison.cpp ├── radius_comparison.h ├── radius_execution_context.cpp ├── radius_execution_context.h ├── settings.cpp ├── settings.h ├── test └── fixtures │ ├── same_wall_different_material_intersecting.ifc │ ├── same_wall_different_material_touching.ifc │ └── schependomlaan_covering.ifc ├── timer.h ├── utils.h ├── writer.cpp └── writer.h /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "IfcOpenShell"] 2 | path = IfcOpenShell 3 | url = https://github.com/tudelft3d/IfcOpenShell 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8.11) 2 | project (tudelft_esri_geobim) 3 | 4 | set(OCC_LIBRARY_NAMES TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKOffset 5 | TKPrim TKBool TKBO TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKHLR TKFeat) 6 | 7 | # IFCOPENSHELL_ROOT 8 | # or 9 | # BOOST_ROOT 10 | # 11 | 12 | OPTION(USE_STATIC_MSVC_RUNTIME "Link to the static runtime on MSVC." OFF) 13 | 14 | include(CheckCXXCompilerFlag) 15 | CHECK_CXX_COMPILER_FLAG("-std=c++14" COMPILER_SUPPORTS_CXX14) 16 | if(COMPILER_SUPPORTS_CXX14) 17 | add_definitions(-std=c++14) 18 | else() 19 | message(FATAL_ERROR "requires a compiler with C++14 support") 20 | endif() 21 | 22 | if(UNIX) 23 | if(APPLE) 24 | set(IFCOPENSHELL_INSTALL ${IFCOPENSHELL_ROOT}/build/Darwin/x86_64/10.9/install) 25 | else() 26 | set(IFCOPENSHELL_INSTALL ${IFCOPENSHELL_ROOT}/build/Linux/x86_64/install) 27 | endif() 28 | 29 | foreach(occt_dir occt-7.3.0 occt-7.3.0p3) 30 | if(EXISTS "${IFCOPENSHELL_INSTALL}/${occt_dir}/") 31 | set(OCCT_PATH "${IFCOPENSHELL_INSTALL}/${occt_dir}/") 32 | endif() 33 | endforeach() 34 | 35 | set(OCC_LIBRARY_DIR ${OCCT_PATH}/lib) 36 | set(OCC_INCLUDE_DIR ${OCCT_PATH}/include/opencascade) 37 | 38 | foreach(libname ${OCC_LIBRARY_NAMES}) 39 | unset(lib CACHE) 40 | find_library(lib NAMES "${libname}" PATHS "${OCC_LIBRARY_DIR}" NO_DEFAULT_PATH) 41 | list(APPEND OCC_LIBRARIES "${lib}") 42 | endforeach() 43 | 44 | if(NOT APPLE) 45 | set(LIB_RT rt) 46 | endif() 47 | 48 | find_package(Threads) 49 | find_library(dl NAMES dl) 50 | if(NOT APPLE) 51 | set(OCC_LIBRARIES -Wl,--start-group ${OCC_LIBRARIES} -Wl,--end-group ${CMAKE_THREAD_LIBS_INIT} ${dl}) 52 | endif() 53 | 54 | add_definitions(-Wno-deprecated-declarations) 55 | else() 56 | set(MSVC_YEAR 2017) 57 | if ("${MSVC_VERSION}" STREQUAL "1900") 58 | set(MSVC_YEAR 2015) 59 | endif() 60 | 61 | file(TO_CMAKE_PATH "${IFCOPENSHELL_ROOT}" IFCOPENSHELL_ROOT) 62 | if (EXISTS ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/oce/Win32/lib) 63 | set(OCC_LIBRARY_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/oce/Win32/lib) 64 | else() 65 | set(OCC_LIBRARY_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/opencascade-7.3.0/win32/lib) 66 | endif() 67 | 68 | set(OCC_INCLUDE_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/oce/include/oce 69 | ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/opencascade-7.3.0/inc) 70 | 71 | add_definitions(-D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS -D_UNICODE) 72 | 73 | string(REGEX REPLACE "([^;]+)" "${OCC_LIBRARY_DIR}/\\1.lib" OCC_LIBRARIES "${OCC_LIBRARY_NAMES}") 74 | 75 | set(Boost_USE_STATIC_LIBS ON) 76 | set(Boost_USE_MULTITHREADED ON) 77 | 78 | if(USE_STATIC_MSVC_RUNTIME) 79 | set(Boost_USE_STATIC_RUNTIME ON) 80 | 81 | # Copied from IfcOpenShell to match compilation flags, todo detect whether necessary 82 | FOREACH(flag CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL 83 | CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE 84 | CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) 85 | IF(${flag} MATCHES "/MD") 86 | STRING(REGEX REPLACE "/MD" "/MT" ${flag} "${${flag}}") 87 | ENDIF() 88 | IF(${flag} MATCHES "/MDd") 89 | STRING(REGEX REPLACE "/MDd" "/MTd" ${flag} "${${flag}}") 90 | ENDIF() 91 | ENDFOREACH() 92 | endif() 93 | 94 | set(WS2_LIBRARIES ws2_32.lib) 95 | endif() 96 | 97 | set(BOOST_COMPONENTS regex program_options iostreams system thread) 98 | 99 | # Find IfcOpenShell version 100 | file(READ "${IFCOPENSHELL_ROOT}/src/ifcparse/IfcParse.h" header) 101 | string(REGEX MATCH "#define IFCOPENSHELL_VERSION \"[0-9a-zA-Z\\.\\-]+\"" ifcopenshell_version "${header}") 102 | string(REGEX MATCH "[0-9]\\.[0-9]" ifcopenshell_major_version "${ifcopenshell_version}") 103 | message(STATUS "IfcOpenShell version ${ifcopenshell_major_version}") 104 | 105 | set(IFC_LIBRARY_NAMES IfcGeom IfcParse geometry_kernel_cgal geometry_kernel_opencascade geometry_kernels geometry_mapping_ifc2x3 geometry_mapping_ifc4 geometry_mapping_ifc4x1 geometry_mapping_ifc4x2 geometry_mappings) 106 | 107 | if (UNIX) 108 | set(IFC_LIBRARY_NAMES ${IFC_LIBRARY_NAMES} ${IFC_LIBRARY_NAMES}) 109 | endif() 110 | 111 | if(UNIX) 112 | set(BOOST_ROOT ${IFCOPENSHELL_INSTALL}/boost-1.69.0) 113 | 114 | set(EIGEN_DIR ${IFCOPENSHELL_INSTALL}/eigen-3.3.7) 115 | 116 | set(JSON_DIR ${IFCOPENSHELL_INSTALL}/json) 117 | 118 | set(CGAL_INCLUDE_DIR ${IFCOPENSHELL_INSTALL}/cgal-5.2/include) 119 | set(CGAL_LIBRARY_DIR ${IFCOPENSHELL_INSTALL}/cgal-5.2/lib) 120 | 121 | file(GLOB CGAL_LIBRARIES "${CGAL_LIBRARY_DIR}/*.a") 122 | 123 | set(GMP_INCLUDE_DIR ${IFCOPENSHELL_INSTALL}/gmp-6.1.2/include) 124 | set(GMP_LIBRARY_DIR ${IFCOPENSHELL_INSTALL}/gmp-6.1.2/lib) 125 | 126 | file(GLOB GMP_LIBRARIES "${GMP_LIBRARY_DIR}/*.a") 127 | 128 | set(MPFR_INCLUDE_DIR ${IFCOPENSHELL_INSTALL}/mpfr-3.1.5/include) 129 | set(MPFR_LIBRARY_DIR ${IFCOPENSHELL_INSTALL}/mpfr-3.1.5/lib) 130 | 131 | file(GLOB MPFR_LIBRARIES "${MPFR_LIBRARY_DIR}/*.a") 132 | 133 | set(IFC_INCLUDE_DIR ${IFCOPENSHELL_ROOT}/src) 134 | set(IFC_LIBRARY_DIR ${IFCOPENSHELL_INSTALL}/ifcopenshell/lib) 135 | 136 | string(REGEX REPLACE "([^;]+)" "${IFC_LIBRARY_DIR}/lib\\1.a" IFC_LIBRARIES "${IFC_LIBRARY_NAMES}") 137 | else() 138 | set(BOOST_ROOT ${IFCOPENSHELL_ROOT}/deps/boost_1_67_0) 139 | set(BOOST_LIBRARYDIR ${IFCOPENSHELL_ROOT}/deps/boost_1_67_0/stage/vs${MSVC_YEAR}-Win32/lib) 140 | 141 | set(EIGEN_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/Eigen) 142 | 143 | set(JSON_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/json) 144 | 145 | set(CGAL_INCLUDE_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/cgal/include) 146 | set(CGAL_LIBRARY_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/cgal/lib) 147 | 148 | file(GLOB CGAL_LIBRARIES "${CGAL_LIBRARY_DIR}/*.lib") 149 | 150 | set(GMP_INCLUDE_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/mpir) 151 | set(GMP_LIBRARY_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/mpir) 152 | 153 | file(GLOB GMP_LIBRARIES "${GMP_LIBRARY_DIR}/*.lib") 154 | 155 | set(MPFR_INCLUDE_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/mpfr) 156 | set(MPFR_LIBRARY_DIR ${IFCOPENSHELL_ROOT}/deps-vs${MSVC_YEAR}-x86-installed/mpfr) 157 | 158 | file(GLOB MPFR_LIBRARIES "${MPFR_LIBRARY_DIR}/*.lib") 159 | 160 | set(IFC_INCLUDE_DIR ${IFCOPENSHELL_ROOT}/src) 161 | set(IFC_LIBRARY_DIR ${IFCOPENSHELL_ROOT}/build-vs${MSVC_YEAR}-x86/${CMAKE_BUILD_TYPE}) 162 | 163 | string(REGEX REPLACE "([^;]+)" "${IFC_LIBRARY_DIR}/\\1.lib" IFC_LIBRARIES "${IFC_LIBRARY_NAMES}") 164 | endif() 165 | 166 | if (MSVC) 167 | add_definitions(/bigobj) 168 | endif() 169 | 170 | find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS}) 171 | 172 | message(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}") 173 | message(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}") 174 | 175 | include_directories(${OCC_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ${IFC_INCLUDE_DIR} 176 | ${EIGEN_DIR} ${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${JSON_DIR}) 177 | link_directories(${Boost_LIBRARY_DIRS}) 178 | 179 | file(GLOB tudelft_esri_geobim_src "*.h" "*.cpp") 180 | 181 | add_executable(tudelft_esri_geobim ${tudelft_esri_geobim_src}) 182 | target_link_libraries(tudelft_esri_geobim ${IFC_LIBRARIES} ${OCC_LIBRARIES} ${CGAL_LIBRARIES} 183 | ${MPFR_LIBRARIES} ${GMP_LIBRARIES} ${WS2_LIBRARIES} ${Boost_LIBRARIES}) 184 | 185 | install(TARGETS tudelft_esri_geobim RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GeoBIM application 2 | ================== 3 | 4 | > #### :warning: Update July 2023 5 | > 6 | > This repository is now also available as a submodule in IfcOpenShell IfcConvert. Download automated builds from https://github.com/IfcOpenBot/IfcOpenShell/commit/2ac66571b05cede41495cec748989435ea74947d#commitcomment-107123988 7 | 8 | Building Information Modelling (BIM) is a fairly novel paradigm in the 9 | construction sector in which a multi-disciplinary information in 10 | construction is exchanged between stakeholders as a decomposition of 11 | individual elements. These elements are defined as a solid volume 12 | geometry that is procedurally defined, semantic information associated 13 | to elements as key-value pairs (property sets) and relationships between 14 | these elements such as decomposition relationships and wall and space 15 | boundary connectivity. 16 | 17 | Conversely in the field of Geographic Information Systems (GIS) a higher 18 | level view is required and there is a stronger focus on functional 19 | characteristics (for the purpose of e.g Facility Management) and spatial 20 | analysis. Due to these discrepancies in information management a direct 21 | conversion of an IFC building model to, for example, CityGML (the 22 | predominant standard for semantic city models) is suboptimal: the 23 | individual building elements do not form a complete manifold shell that 24 | separates interior and exterior and there is geometric informtion in the 25 | model that might be too detailed for this purpose. 26 | 27 | This application offers a transformation from IFC to CityGML (CityJSON 28 | to be precise) that takes into account these considerations. It is a 29 | reimplemetation of the work in: 30 | 31 | > Donkers, S., Ledoux, H., Zhao, J., & Stoter, J. (2016). Automatic conversion of IFC datasets to 32 | > geometrically and semantically correct CityGML LOD3 buildings. Transactions in GIS, 20(4), 547-569. 33 | 34 | In short, interpreted geometries in IFC building models are enlarged 35 | (dilated) as the Minkowski sum of the IFC volumes and a constant size 36 | polyhedron (small cube in our case for now). The resulting geometries are 37 | all accumulated as the Boolean sum of the enlarged element volumes. 38 | 39 | A specific branch of IfcOpenShell is used that can use CGAL to process the 40 | element geometries. The application uses the Nef Polyhedron 3 and Minkowski 41 | sum packages from CGAL. 42 | 43 | Compilation 44 | ----------- 45 | 46 | The application relies on a subset of the dependencies of IfcOpenShell. 47 | Therefore easiest is to install IfcOpenShell first. The CMake file in this 48 | repository can identify the dependencies as installed by the IfcOpenShell 49 | build script. This makes compilation of this executable a fairly trivial, 50 | but lengthy process (the compilation of all IfcOpenShell dependencies takes 51 | about an hour on average hardware, five minutes on 32 core machine). 52 | 53 | git clone --recursive https://github.com/tudelft3d/esri_geobim 54 | cd esri_geobim/IfcOpenShell/nix 55 | apt-get install git gcc g++ autoconf bison make \ 56 | libfreetype6-dev mesa-common-dev libffi-dev cmake 57 | # either: 58 | python build-all.py IfcConvert 59 | # or 60 | CC=/usr/bin/clang-10 CXX=/usr/bin/clang++-10 python2 build-all.py IfcConvert 61 | cd ../../ 62 | mkdir build 63 | cd build 64 | cmake .. -DIFCOPENSHELL_ROOT=`pwd`/../IfcOpenShell 65 | make 66 | ./tudelft_esri_geobim 67 | 68 | Instructions for Windows and for compilation using preexisting dependencies 69 | will follow. 70 | 71 | Features 72 | -------- 73 | 74 | ### Closing gaps in the facade 75 | 76 | The formation of a complete manifold shell that separates interior from exterior depends on the individual building elements providing this separation. (Small) gaps in this facade are closed by means of the dilation radius that makes the invidual elements slightly larger so that they overlap. This dilation radius is (currently) a global value, so increasing this radius to close gaps might mean loss of geometric detail elsewhere. For this purpose there is a specific feature to overlay results for two radii and create IFC geometries that fill the gaps so that by incorporating these newly constructed elements a smaller global radius can be used preserving geometric detail. 77 | 78 | 79 | ### Gap radius finding using binary search 80 | 81 | 82 | ### Validating IsExternal property 83 | 84 | 85 | Performance 86 | ----------- 87 | 88 | CGAL number types 89 | 90 | The robustness of the implementation in CGAL stems from what they call exact number types: trees of operands that keep their history and more precision bits than available in hardware. Interval types to encode the uncertainty as a pair of efficient machine native floating points and a fallback to the precise numbers when the interval arithmetic is uncertain. 91 | 92 | Minkowski 93 | 94 | 95 | 96 | Padding volume 97 | 98 | 99 | 2D 100 | 101 | Openings posthoc 102 | 103 | Vertex moving 104 | 105 | Multi-threading 106 | 107 | Reference counted mutable shared number types (trees of operands). Makes multi-threading difficult. Not only writing, but also reading, as a read can update the "lazy exact" representation. Extra care not to use static data fields such as a global Z axis. 108 | 109 | Voxel preselection 110 | 111 | Minkowski is O(V), voxel is O(m), so by using a coarse grid size the complexity can be greatly reduced. IFC interpretation could be re-used but is currently not implemented as the voxelization code base is based on the traditional IfcOpenShell version that relies on OpenCASCADE. 112 | 113 | 114 | Results 115 | ------- 116 | 117 | Duplex 118 | 119 | Schependomlaan 120 | 121 | Riverside LOD 300 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /context.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTEXT_H 2 | #define CONTEXT_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | // Generated for every representation *item* in the IFC file 12 | struct shape_callback_item { 13 | IfcUtil::IfcBaseEntity* src; 14 | std::string id, type, part_reference, geom_reference; 15 | cgal_placement_t transformation; 16 | cgal_shape_t polyhedron; 17 | const ifcopenshell::geometry::taxonomy::style* style; 18 | boost::optional wall_direction; 19 | std::list openings; 20 | 21 | bool to_nef_polyhedron(CGAL::Nef_polyhedron_3& nef, bool copy=false); 22 | }; 23 | 24 | // Prototype of a context to which processed shapes will be fed 25 | struct execution_context { 26 | virtual void operator()(shape_callback_item*) = 0; 27 | }; 28 | 29 | #endif -------------------------------------------------------------------------------- /geobim.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************** 2 | * * 3 | * This file is part of TUDelft Esri GEOBIM. * 4 | * * 5 | * License: APACHE * 6 | * * 7 | ********************************************************************************/ 8 | 9 | #include "timer.h" 10 | #include "writer.h" 11 | #include "settings.h" 12 | #include "processing.h" 13 | #include "radius_execution_context.h" 14 | #include "global_execution_context.h" 15 | #include "radius_comparison.h" 16 | 17 | #include 18 | #include 19 | 20 | namespace { 21 | template 22 | struct output_writer { 23 | W& w; 24 | 25 | template 26 | void operator()(R& r) { 27 | for (auto& p : r) { 28 | w(p.first, p.second.begin(), p.second.end()); 29 | } 30 | } 31 | }; 32 | } 33 | 34 | int main(int argc, char** argv) { 35 | Logger::SetOutput(&std::cerr, &std::cerr); 36 | Logger::Verbosity(Logger::LOG_NOTICE); 37 | 38 | geobim_settings settings; 39 | parse_command_line(settings, argc, argv); 40 | 41 | // global_execution_context> global_context; 42 | // Epick seems to have better performance? 43 | global_execution_context global_context; 44 | global_execution_context global_context_exact; 45 | 46 | shape_callback callback_global; 47 | if (settings.exact_segmentation) { 48 | callback_global.contexts.push_back(&global_context_exact); 49 | } else { 50 | callback_global.contexts.push_back(&global_context); 51 | } 52 | 53 | #ifdef GEOBIM_DEBUG 54 | callback.contexts.push_back(new debug_writer); 55 | #endif 56 | 57 | std::unique_ptr p; 58 | 59 | if (settings.radii.empty()) { 60 | // @todo 61 | /* 62 | auto cec = new capturing_execution_context; 63 | callback.contexts.push_back(cec); 64 | p = std::make_unique(settings); 65 | (*p)(std::ref(callback)); 66 | auto R = binary_search(cec->items.begin(), cec->items.end(), { "0.001", "0.2" }); 67 | std::cout << "Largest gap found with R / 2 ~ " << R << std::endl; 68 | */ 69 | } else { 70 | std::vector> radius_contexts; 71 | bool first = true; 72 | for (auto& r : settings.radii) { 73 | // 2nd is narrower (depending on ifdef above, appears to be necessary). 74 | radius_contexts.push_back(std::make_unique(r, radius_settings() 75 | .set(radius_settings::NARROWER, !first) 76 | .set(radius_settings::MINKOWSKI_TRIANGLES, settings.minkowski_triangles) 77 | .set(radius_settings::NO_EROSION, settings.no_erosion) 78 | .set(radius_settings::SPHERE, settings.spherical_padding))); 79 | first = false; 80 | if (settings.threads) { 81 | radius_contexts.back()->set_threads(*settings.threads); 82 | } 83 | } 84 | 85 | capturing_execution_context cec; 86 | 87 | p = std::make_unique(settings); 88 | (*p)(std::ref(cec)); 89 | 90 | { 91 | shape_callback callback; 92 | for (auto& c : radius_contexts) { 93 | callback.contexts.push_back(&*c); 94 | } 95 | 96 | cec.run(std::ref(callback)); 97 | } 98 | 99 | cec.run(std::ref(callback_global)); 100 | 101 | std::cout << "done processing geometries" << std::endl; 102 | 103 | for (auto& f : settings.file) { 104 | delete f; 105 | } 106 | 107 | auto T1 = timer::measure("semantic_segmentation"); 108 | if (settings.exact_segmentation) { 109 | global_context_exact.finalize(); 110 | } else { 111 | global_context.finalize(); 112 | } 113 | T1.stop(); 114 | 115 | for (auto& c : radius_contexts) { 116 | if (c->empty()) { 117 | continue; 118 | } 119 | 120 | c->finalize(); 121 | } 122 | 123 | p.reset(); 124 | 125 | for (auto& c : radius_contexts) { 126 | auto T0 = timer::measure("semantic_segmentation"); 127 | 128 | std::list all_infos; 129 | 130 | if (settings.exact_segmentation) { 131 | all_infos = global_context_exact.all_item_infos(); 132 | } 133 | else { 134 | all_infos = global_context.all_item_infos(); 135 | } 136 | 137 | // pop the first 'empty' info 138 | all_infos.pop_front(); 139 | 140 | city_json_writer write_city(settings.output_filename + c->radius_str); 141 | simple_obj_writer write_obj(settings.output_filename + c->radius_str); 142 | external_element_collector write_elem(settings.output_filename + ".external", all_infos); 143 | 144 | boost::variant< 145 | global_execution_context::segmentation_return_type, 146 | global_execution_context::segmentation_return_type_2 147 | > style_facet_pairs; 148 | 149 | if (settings.spherical_padding) { 150 | if (settings.exact_segmentation) { 151 | style_facet_pairs = global_context_exact.segment(c->polyhedron_exterior_nm); 152 | } else { 153 | style_facet_pairs = global_context.segment(c->polyhedron_exterior_nm); 154 | } 155 | write_city.point_lookup = &c->polyhedron_exterior_nm.points; 156 | write_obj.point_lookup = &c->polyhedron_exterior_nm.points; 157 | write_elem.point_lookup = &c->polyhedron_exterior_nm.points; 158 | } else if (settings.exact_segmentation) { 159 | style_facet_pairs = global_context_exact.segment(c->polyhedron_exterior); 160 | } else { 161 | style_facet_pairs = global_context.segment(c->polyhedron_exterior); 162 | } 163 | 164 | T0.stop(); 165 | 166 | { 167 | output_writer vis{ write_city }; 168 | boost::apply_visitor(vis, style_facet_pairs); 169 | write_city.finalize(); 170 | } 171 | 172 | { 173 | output_writer vis{ write_obj }; 174 | boost::apply_visitor(vis, style_facet_pairs); 175 | write_obj.finalize(); 176 | } 177 | 178 | { 179 | output_writer vis{ write_elem }; 180 | boost::apply_visitor(vis, style_facet_pairs); 181 | write_elem.finalize(); 182 | } 183 | } 184 | 185 | auto T2 = timer::measure("difference_overlay"); 186 | auto it = radius_contexts.begin(); 187 | for (auto jt = it + 1; jt != radius_contexts.end(); ++it, ++jt) { 188 | radius_comparison difference(**it, **jt, 0.001); 189 | simple_obj_writer obj("difference-" 190 | + (*it)->radius_str + "-" 191 | + (*jt)->radius_str); 192 | obj(nullptr, difference.difference_poly.facets_begin(), difference.difference_poly.facets_end()); 193 | } 194 | T2.stop(); 195 | 196 | } 197 | 198 | timer::print(std::cout); 199 | } 200 | -------------------------------------------------------------------------------- /global_execution_context.cpp: -------------------------------------------------------------------------------- 1 | #include "global_execution_context.h" 2 | 3 | #include "utils.h" 4 | 5 | template 6 | global_execution_context::global_execution_context() 7 | #ifdef GEOBIM_DEBUG 8 | : obj_("debug-tree") 9 | #endif 10 | { 11 | // style 0 is for elements without style annotations 12 | infos.push_back(nullptr); 13 | } 14 | 15 | template 16 | void global_execution_context::finalize() { 17 | tree.build(); 18 | tree.accelerate_distance_queries(); 19 | } 20 | 21 | // @todo this should be merged with the other implementation below. 22 | template 23 | typename global_execution_context::segmentation_return_type_2 global_execution_context::segment(const non_manifold_polyhedron>& input) { 24 | segmentation_return_type_2 result(infos.size()); 25 | auto it = infos.begin(); 26 | for (size_t i = 0; i < infos.size(); ++i, ++it) { 27 | result[i].first = i ? *it : nullptr; 28 | } 29 | 30 | CGAL::Cartesian_converter, TreeKernel> converter; 31 | 32 | for (auto it = input.indices.begin(); it != input.indices.end(); ++it) { 33 | 34 | auto f_c = CGAL::centroid( 35 | input.points[(*it)[0]], 36 | input.points[(*it)[1]], 37 | input.points[(*it)[2]] 38 | ); 39 | 40 | auto f_c_ic = converter(f_c); 41 | 42 | auto pair = tree.closest_point_and_primitive(f_c_ic); 43 | typename TreeShapeType::Face_handle F = pair.second.first; 44 | auto jt = facet_to_info.find(F); 45 | if (jt != facet_to_info.end()) { 46 | int sid = std::distance(infos.cbegin(), std::find(infos.cbegin(), infos.cend(), jt->second)); 47 | result[sid].second.push_back(it); 48 | } 49 | } 50 | 51 | return result; 52 | } 53 | 54 | template 55 | typename global_execution_context::segmentation_return_type global_execution_context::segment(const cgal_shape_t & input) { 56 | segmentation_return_type result(infos.size()); 57 | auto it = infos.begin(); 58 | for (size_t i = 0; i < infos.size(); ++i, ++it) { 59 | result[i].first = i ? *it : nullptr; 60 | } 61 | 62 | CGAL::Cartesian_converter converter; 63 | 64 | for (auto &f : faces(input)) { 65 | 66 | auto f_c = CGAL::centroid( 67 | f->facet_begin()->vertex()->point(), 68 | f->facet_begin()->next()->vertex()->point(), 69 | f->facet_begin()->next()->next()->vertex()->point() 70 | ); 71 | 72 | auto f_c_ic = converter(f_c); 73 | 74 | #if 0 75 | // Find co-planar facets from original IFC geometries within bounding box of 76 | // nef-constructed facet. Filter based on facet normal and distance along normal. 77 | 78 | std::list primitives, coplanar_primitives; 79 | Bounding_box f_bbox = CGAL::Polygon_mesh_processing::face_bbox(f, input); 80 | tree.all_intersected_primitives(f_bbox, std::back_inserter(primitives)); 81 | 82 | auto f_norm = CGAL::Polygon_mesh_processing::compute_face_normal(f, input); 83 | auto f_norm_ic = converter(f_norm); 84 | 85 | for (auto& p : primitives) { 86 | auto p_norm_ic = CGAL::Polygon_mesh_processing::compute_face_normal(p.first, *p.second); 87 | auto dot = std::abs(CGAL::to_double(f_norm_ic * p_norm_ic)); 88 | if (dot > 0.999) { 89 | auto p_p0_ic = p.first->facet_begin()->vertex()->point(); 90 | auto d = std::abs(CGAL::to_double((p_p0_ic - f_c_ic) * p_norm_ic)); 91 | if (d < 0.001) { 92 | coplanar_primitives.push_back(p); 93 | } 94 | } 95 | } 96 | 97 | std::cout << "bbox " << primitives.size() << " coplanar " << coplanar_primitives.size() << std::endl; 98 | #endif 99 | 100 | auto pair = tree.closest_point_and_primitive(f_c_ic); 101 | typename TreeShapeType::Face_handle F = pair.second.first; 102 | auto it = facet_to_info.find(F); 103 | if (it != facet_to_info.end()) { 104 | int sid = std::distance(infos.cbegin(), std::find(infos.cbegin(), infos.cend(), it->second)); 105 | result[sid].second.push_back(f); 106 | } 107 | } 108 | 109 | return result; 110 | } 111 | 112 | template 113 | void global_execution_context::operator()(shape_callback_item* item) { 114 | rgb* diffuse = item->style ? new rgb(item->style->diffuse.ccomponents()) : (rgb*) nullptr; 115 | item_info* info = new item_info{ 116 | item->src->declaration().name(), 117 | item->src->get_value("GlobalId"), 118 | diffuse 119 | }; 120 | infos.push_back(info); 121 | 122 | // typename TreeKernel::Aff_transformation_3 transformation; 123 | // util::copy::transformation(transformation, item->transformation); 124 | 125 | std::transform( 126 | item->polyhedron.points_begin(), item->polyhedron.points_end(), 127 | item->polyhedron.points_begin(), item->transformation); 128 | 129 | TreeShapeType tree_polyhedron; 130 | util::copy::polyhedron(tree_polyhedron, item->polyhedron); 131 | 132 | triangulated_shape_memory.push_back(tree_polyhedron); 133 | CGAL::Polygon_mesh_processing::triangulate_faces(triangulated_shape_memory.back()); 134 | tree.insert(faces(triangulated_shape_memory.back()).first, faces(triangulated_shape_memory.back()).second, triangulated_shape_memory.back()); 135 | 136 | #ifdef GEOBIM_DEBUG 137 | obj_(nullptr, triangulated_shape_memory.back().facets_begin(), triangulated_shape_memory.back().facets_end()); 138 | #endif 139 | 140 | for (auto& f : faces(triangulated_shape_memory.back())) { 141 | typename TreeShapeType::Facet_handle F = f; 142 | facet_to_info.insert({ F, info }); 143 | } 144 | } 145 | 146 | template struct global_execution_context; 147 | template struct global_execution_context; 148 | template struct global_execution_context>; 149 | -------------------------------------------------------------------------------- /global_execution_context.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_EXECUTION_CONTEXT_H 2 | #define GLOBAL_EXECUTION_CONTEXT_H 3 | 4 | #include "processing.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // State that is relevant accross the different radii for which the 11 | // program is executed. Used to map back the semantics from the original 12 | // IFC input to the newly constructed polyhedra. 13 | template 14 | struct global_execution_context : public execution_context { 15 | typedef CGAL::Polyhedron_3 TreeShapeType; 16 | typedef CGAL::AABB_face_graph_triangle_primitive Primitive; 17 | typedef CGAL::AABB_traits AAbbTraits; 18 | typedef typename AAbbTraits::Bounding_box Bounding_box; 19 | typedef CGAL::AABB_tree AAbbTree; 20 | typedef typename AAbbTree::Primitive_id Primitive_id; 21 | 22 | typedef std::vector>> segmentation_return_type; 25 | 26 | typedef std::vector>::const_iterator>>> segmentation_return_type_2; 29 | 30 | AAbbTree tree; 31 | 32 | std::list infos; 33 | 34 | // A reference is kept to the original shapes in a std::list. 35 | // Later an aabb tree is used map eroded triangle centroids 36 | // back to the original elements to preserve semantics. 37 | std::list triangulated_shape_memory; 38 | 39 | // A pointer only to conserve memory 40 | std::map facet_to_info; 41 | 42 | #ifdef GEOBIM_DEBUG 43 | simple_obj_writer obj_; 44 | #endif 45 | 46 | global_execution_context(); 47 | 48 | void operator()(shape_callback_item* item); 49 | 50 | void finalize(); 51 | 52 | segmentation_return_type segment(const cgal_shape_t& input); 53 | 54 | typedef CGAL::Simple_cartesian::Point_3 P3; 55 | std::vector* point_lookup; 56 | segmentation_return_type_2 segment(const non_manifold_polyhedron>& input); 57 | 58 | const std::list& all_item_infos() const { return infos; } 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /opening_collector.cpp: -------------------------------------------------------------------------------- 1 | #include "opening_collector.h" 2 | 3 | void opening_collector::init(IfcParse::IfcFile * f) { 4 | auto rels = f->instances_by_type("IfcRelVoidsElement"); 5 | if (!rels) { 6 | return; 7 | } 8 | for (auto& rel : *rels) { 9 | auto be = (IfcUtil::IfcBaseEntity*) ((IfcUtil::IfcBaseEntity*)rel)->get_value("RelatingBuildingElement"); 10 | auto op = (IfcUtil::IfcBaseEntity*) ((IfcUtil::IfcBaseEntity*)rel)->get_value("RelatedOpeningElement"); 11 | opening_to_elem.insert({ op, be }); 12 | } 13 | } 14 | 15 | opening_collector::opening_collector(IfcParse::IfcFile * f) { 16 | init(f); 17 | } 18 | 19 | opening_collector::opening_collector(const std::vector& fs) { 20 | for (auto f : fs) { 21 | init(f); 22 | } 23 | } 24 | 25 | void opening_collector::operator()(shape_callback_item* item) { 26 | auto opit = opening_to_elem.find(item->src); 27 | if (opit != opening_to_elem.end()) { 28 | list.push_back(item); 29 | map.insert({ opit->second, list.back() }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /opening_collector.h: -------------------------------------------------------------------------------- 1 | #ifndef OPENING_COLLECTOR_H 2 | #define OPENING_COLLECTOR_H 3 | 4 | #include "context.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | // Collects openings in the file and maps them to their parent elements 11 | // for processing them after applying the Minkowski sum, as that performs 12 | // much less well on non-convex inputs. 13 | struct opening_collector : public execution_context { 14 | std::list list; 15 | std::map opening_to_elem; 16 | std::multimap map; 17 | 18 | void init(IfcParse::IfcFile* f); 19 | opening_collector(IfcParse::IfcFile* f); 20 | opening_collector(const std::vector& f); 21 | void operator()(shape_callback_item* item); 22 | 23 | private: 24 | opening_collector(const opening_collector&) = delete; 25 | opening_collector& operator=(const opening_collector&) = delete; 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /processing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "processing.h" 4 | #include "writer.h" 5 | #include "opening_collector.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | // #define USE_COPY 12 | 13 | bool shape_callback_item::to_nef_polyhedron(CGAL::Nef_polyhedron_3& nef, bool make_copy) { 14 | auto T1 = timer::measure("ifc_element_to_nef"); 15 | 16 | decltype(polyhedron)* input; 17 | std::unique_ptr copy; 18 | 19 | if (make_copy) { 20 | copy = std::make_unique(); 21 | // this appears to help with multithreading 22 | CGAL::copy_face_graph(polyhedron, *copy); 23 | input = &*copy; 24 | } else { 25 | input = &polyhedron; 26 | } 27 | 28 | 29 | nef = ifcopenshell::geometry::utils::create_nef_polyhedron(*input); 30 | T1.stop(); 31 | 32 | if (nef.is_empty() || !nef.is_simple()) { 33 | nef.clear(); 34 | return false; 35 | } 36 | 37 | return true; 38 | } 39 | 40 | void debug_writer::operator()(shape_callback_item* item) { 41 | simple_obj_writer obj("debug-" + boost::lexical_cast(item->src->data().id())); 42 | obj(nullptr, item->polyhedron.facets_begin(), item->polyhedron.facets_end()); 43 | } 44 | 45 | // Interprets IFC geometries by means of IfcOpenShell CGAL and 46 | // pass result to callback 47 | process_geometries::process_geometries(geobim_settings& s) 48 | : settings(s), all_openings(settings.file) 49 | {} 50 | 51 | process_geometries::~process_geometries() { 52 | std::cout << "~process_geometries()" << std::endl; 53 | for (auto& x : iterators) { 54 | delete x; 55 | } 56 | } 57 | 58 | int process_geometries::operator()(const std::function& fn) { 59 | // Capture all openings beforehand, they are later assigned to the 60 | // building elements. 61 | auto opening_settings = settings; 62 | opening_settings.entity_names.emplace(); 63 | opening_settings.entity_names->insert("IfcOpeningElement"); 64 | opening_settings.entity_names_included = true; 65 | if (settings.apply_openings_posthoc && settings.entity_names != opening_settings.entity_names) { 66 | process_geometries p(opening_settings); 67 | p(std::ref(all_openings)); 68 | } 69 | 70 | std::vector filters; 71 | if (settings.entity_names) { 72 | if (!settings.entity_names_included) { 73 | settings.entity_names->insert("IfcSpace"); 74 | settings.entity_names->insert("IfcOpeningElement"); 75 | } 76 | filters.push_back(IfcGeom::entity_filter(settings.entity_names_included, false, *settings.entity_names)); 77 | } else { 78 | filters.push_back(IfcGeom::entity_filter(false, false, { "IfcSpace", "IfcOpeningElement" })); 79 | } 80 | 81 | for (auto f : settings.file) { 82 | 83 | auto ci = new ifcopenshell::geometry::Iterator("cgal", settings.settings, f, filters); // does not seem to help: , std::thread::hardware_concurrency()); 84 | iterators.push_back(ci); 85 | auto& context_iterator = *ci; 86 | 87 | auto T = timer::measure("ifc_geometry_processing"); 88 | if (!context_iterator.initialize()) { 89 | continue; 90 | } 91 | T.stop(); 92 | 93 | size_t num_created = 0; 94 | 95 | auto axis_settings = settings.settings; 96 | axis_settings.set(ifcopenshell::geometry::settings::EXCLUDE_SOLIDS_AND_SURFACES, true); 97 | axis_settings.set(ifcopenshell::geometry::settings::INCLUDE_CURVES, true); 98 | auto geometry_mapper = ifcopenshell::geometry::impl::mapping_implementations().construct(f, axis_settings); 99 | 100 | for (;; ++num_created) { 101 | bool has_more = true; 102 | if (num_created) { 103 | auto T0 = timer::measure("ifc_geometry_processing"); 104 | has_more = context_iterator.next(); 105 | T0.stop(); 106 | } 107 | ifcopenshell::geometry::NativeElement* geom_object = nullptr; 108 | if (has_more) { 109 | geom_object = context_iterator.get_native(); 110 | } 111 | if (!geom_object) { 112 | break; 113 | } 114 | 115 | if (geom_object->guid() == "3es57B9Kr3nxL4uBITV$0e") { 116 | std::cout << "NOTICE Skipping: " << geom_object->product()->data().toString() << std::endl; 117 | continue; 118 | } 119 | 120 | std::cout << "Processing: " << geom_object->product()->data().toString() << std::endl; 121 | 122 | const auto& n = geom_object->transformation().data().ccomponents(); 123 | const cgal_placement_t element_transformation( 124 | n(0, 0), n(0, 1), n(0, 2), n(0, 3), 125 | n(1, 0), n(1, 1), n(1, 2), n(1, 3), 126 | n(2, 0), n(2, 1), n(2, 2), n(2, 3)); 127 | 128 | boost::optional wall_direction; 129 | 130 | 131 | std::list openings; 132 | 133 | auto p = all_openings.map.equal_range(geom_object->product()); 134 | for (auto it = p.first; it != p.second; ++it) { 135 | openings.push_back(it->second); 136 | } 137 | 138 | std::cout << openings.size() << " openings" << std::endl; 139 | 140 | if (settings.apply_openings_posthoc && geom_object->product()->declaration().is("IfcWall")) { 141 | auto T2 = timer::measure("wall_axis_handling"); 142 | auto item = geometry_mapper->map(geom_object->product()); 143 | if (item) { 144 | typedef ifcopenshell::geometry::taxonomy::collection cl; 145 | typedef ifcopenshell::geometry::taxonomy::loop l; 146 | typedef ifcopenshell::geometry::taxonomy::edge e; 147 | typedef ifcopenshell::geometry::taxonomy::point3 p; 148 | 149 | if (item->kind() == ifcopenshell::geometry::taxonomy::COLLECTION && 150 | ((cl*)item)->children.size() == 1 && 151 | ((cl*)item)->children[0]->kind() == ifcopenshell::geometry::taxonomy::COLLECTION && 152 | ((cl*)((cl*)item)->children[0])->children.size() == 1 && 153 | ((cl*)((cl*)item)->children[0])->children[0]->kind() == ifcopenshell::geometry::taxonomy::LOOP && 154 | ((l*)((cl*)((cl*)item)->children[0])->children[0])->children.size() == 1 && 155 | ((l*)((cl*)((cl*)item)->children[0])->children[0])->children[0]->kind() == ifcopenshell::geometry::taxonomy::EDGE) 156 | { 157 | auto edge = (e*)((l*)((cl*)((cl*)item)->children[0])->children[0])->children[0]; 158 | if (edge->basis == nullptr && edge->start.which() == 0 && edge->end.which() == 0) { 159 | const auto& p0 = boost::get

(edge->start); 160 | const auto& p1 = boost::get

(edge->end); 161 | Eigen::Vector4d P0 = p0.ccomponents().homogeneous(); 162 | Eigen::Vector4d P1 = p1.ccomponents().homogeneous(); 163 | auto V0 = n * P0; 164 | auto V1 = n * P1; 165 | std::cout << "Axis " << V0(0) << " " << V0(1) << " " << V0(2) << " -> " 166 | << V1(0) << " " << V1(1) << " " << V1(2) << std::endl; 167 | wall_direction = (V1 - V0).head<3>().normalized(); 168 | } 169 | } 170 | } 171 | T2.stop(); 172 | } 173 | 174 | for (auto& g : geom_object->geometry()) { 175 | auto s = ((ifcopenshell::geometry::CgalShape*) g.Shape())->shape(); 176 | const auto& m = g.Placement().ccomponents(); 177 | 178 | const cgal_placement_t part_transformation( 179 | m(0, 0), m(0, 1), m(0, 2), m(0, 3), 180 | m(1, 0), m(1, 1), m(1, 2), m(1, 3), 181 | m(2, 0), m(2, 1), m(2, 2), m(2, 3)); 182 | 183 | // Apply transformation 184 | for (auto &vertex : vertices(s)) { 185 | vertex->point() = vertex->point().transform(part_transformation); 186 | } 187 | 188 | const ifcopenshell::geometry::taxonomy::style* opt_style = nullptr; 189 | if (g.hasStyle()) { 190 | opt_style = &g.Style(); 191 | } 192 | 193 | shape_callback_item* item = new shape_callback_item{ 194 | geom_object->product(), 195 | geom_object->guid(), 196 | geom_object->type(), 197 | geom_object->geometry().id(), 198 | std::to_string(g.ItemId()), 199 | element_transformation, 200 | s, 201 | opt_style, 202 | wall_direction, 203 | openings 204 | }; 205 | 206 | fn(item); 207 | 208 | std::cout << "Processed: " << geom_object->product()->data().toString() << " part: #" << g.ItemId() << std::endl; 209 | } 210 | 211 | std::cout << "Progress: " << context_iterator.progress() << std::endl; 212 | 213 | } 214 | 215 | } 216 | 217 | return 0; 218 | } 219 | -------------------------------------------------------------------------------- /processing.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESSING_H 2 | #define PROCESSING_H 3 | 4 | #include "timer.h" 5 | #include "context.h" 6 | #include "settings.h" 7 | #include "opening_collector.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | template 16 | struct non_manifold_polyhedron { 17 | typedef CGAL::Point_3 P; 18 | std::vector

points; 19 | std::vector> indices; 20 | }; 21 | 22 | // Light weight representation to be stored in global exec context 23 | struct rgb : public std::pair> { 24 | rgb(double r, double g, double b) 25 | : std::pair>(r, { g, b }) {} 26 | rgb(const Eigen::Vector3d& v) 27 | : std::pair>(v(0), { v(1), v(2) }) {} 28 | 29 | double& r() { return this->first; } 30 | const double& r() const { return this->first; } 31 | 32 | double& g() { return this->second.first; } 33 | const double& g() const { return this->second.first; } 34 | 35 | double& b() { return this->second.second; } 36 | const double& b() const { return this->second.second; } 37 | }; 38 | 39 | // Light weight representation to be stored in global exec context 40 | struct item_info { 41 | // @nb we don't store a reference to the ifcopenshell entity instance so the files can be freed from memory 42 | // we can store a const reference to the ifcopenshell latebound schema type names. 43 | const std::string& entity_type; 44 | std::string guid; 45 | rgb* diffuse; 46 | }; 47 | 48 | struct debug_writer : public execution_context { 49 | void operator()(shape_callback_item* item); 50 | }; 51 | 52 | // An execution context that stores processed items from the file. 53 | struct capturing_execution_context : public execution_context { 54 | std::list items; 55 | 56 | void operator()(shape_callback_item* item) { 57 | items.push_back(item); 58 | } 59 | 60 | template 61 | void run(Fn fn) { 62 | for (auto& i : items) { 63 | fn(i); 64 | } 65 | } 66 | }; 67 | 68 | // A structure for recieving processed shapes simply defers to a vector of contexts 69 | struct shape_callback { 70 | std::vector contexts; 71 | 72 | void operator()(shape_callback_item* item) { 73 | for (auto& c : contexts) { 74 | (*c)(item); 75 | } 76 | } 77 | }; 78 | 79 | // Interprets IFC geometries by means of IfcOpenShell CGAL and 80 | // pass result to callback 81 | struct process_geometries { 82 | geobim_settings settings; 83 | opening_collector all_openings; 84 | std::list iterators; 85 | 86 | process_geometries(geobim_settings&); 87 | ~process_geometries(); 88 | int operator()(const std::function&); 89 | }; 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /radius_comparison.cpp: -------------------------------------------------------------------------------- 1 | #include "radius_comparison.h" 2 | 3 | #include "writer.h" 4 | #include 5 | #include 6 | 7 | CGAL::Nef_polyhedron_3 create_bounding_box(const cgal_shape_t & input, double radius); 8 | 9 | radius_comparison::hollow_solid::hollow_solid(radius_execution_context & a, double d) { 10 | D = d; 11 | 12 | { 13 | cgal_shape_t pl; 14 | simple_obj_writer obj("hollow-input-" + a.radius_str); 15 | obj(nullptr, a.polyhedron_exterior.facets_begin(), a.polyhedron_exterior.facets_end()); 16 | } 17 | 18 | bbox = create_bounding_box(a.polyhedron_exterior, a.radius); 19 | auto exterior = ifcopenshell::geometry::utils::create_nef_polyhedron(a.polyhedron_exterior); 20 | 21 | complement = bbox - exterior; 22 | complement.extract_regularization(); 23 | auto polycube = ifcopenshell::geometry::utils::create_cube(d); 24 | cube = ifcopenshell::geometry::utils::create_nef_polyhedron(polycube); 25 | complement_padded = CGAL::minkowski_sum_3(complement, cube); 26 | complement_padded.extract_regularization(); 27 | inner = bbox - complement_padded; 28 | inner.extract_regularization(); 29 | { 30 | auto inner_poly = ifcopenshell::geometry::utils::create_polyhedron(inner); 31 | simple_obj_writer obj("debug-inner-" + boost::lexical_cast(D)); 32 | obj(nullptr, inner_poly.facets_begin(), inner_poly.facets_end()); 33 | } 34 | hollow = exterior - inner; 35 | hollow.extract_regularization(); 36 | } 37 | 38 | #define MAKE_OP2_NARROWER -2e-7 39 | 40 | radius_comparison::radius_comparison(radius_execution_context & a, radius_execution_context & b, double d) 41 | : A(a, d), B(b, d + MAKE_OP2_NARROWER) { 42 | 43 | { 44 | cgal_shape_t pl; 45 | CGAL::convert_nef_polyhedron_to_polygon_mesh(A.hollow, pl); 46 | CGAL::Polygon_mesh_processing::triangulate_faces(pl); 47 | simple_obj_writer obj("debug-hollow-a"); 48 | obj(nullptr, pl.facets_begin(), pl.facets_end()); 49 | } 50 | 51 | { 52 | cgal_shape_t pl; 53 | CGAL::convert_nef_polyhedron_to_polygon_mesh(B.hollow, pl); 54 | CGAL::Polygon_mesh_processing::triangulate_faces(pl); 55 | simple_obj_writer obj("debug-hollow-b"); 56 | obj(nullptr, pl.facets_begin(), pl.facets_end()); 57 | } 58 | 59 | difference_nef = B.hollow - A.hollow; 60 | difference_nef.extract_regularization(); 61 | // difference_poly = ifcopenshell::geometry::utils::create_polyhedron(difference_nef); 62 | CGAL::convert_nef_polyhedron_to_polygon_mesh(difference_nef, difference_poly); 63 | CGAL::Polygon_mesh_processing::triangulate_faces(difference_poly); 64 | } 65 | -------------------------------------------------------------------------------- /radius_comparison.h: -------------------------------------------------------------------------------- 1 | #ifndef RADIUS_COMPARISON_H 2 | #define RADIUS_COMPARISON_H 3 | 4 | #include "radius_execution_context.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | // A comparison between two exterior-shell polyhedra to identify gaps 11 | // in a facade that are filled by a larger dilation radius but left 12 | // open when using the smaller radius. 13 | struct radius_comparison { 14 | struct hollow_solid { 15 | typedef CGAL::Nef_polyhedron_3 nef; 16 | 17 | nef bbox, complement, complement_padded, inner, hollow, cube; 18 | 19 | double D; 20 | 21 | hollow_solid(radius_execution_context& a, double d); 22 | }; 23 | 24 | typedef CGAL::Nef_polyhedron_3 nef; 25 | typedef CGAL::Polyhedron_3 poly; 26 | 27 | nef difference_nef; 28 | poly difference_poly; 29 | hollow_solid A, B; 30 | 31 | radius_comparison(radius_execution_context& a, radius_execution_context& b, double d); 32 | }; 33 | 34 | template 35 | double initialize_radius_context_and_get_volume_with_cache(It first, It second, const std::string& radius) { 36 | static std::map cache_; 37 | 38 | auto it = cache_.find(radius); 39 | if (it != cache_.end()) { 40 | std::cout << "Used cache for R=" << radius << " V=" << it->second << std::endl; 41 | return it->second; 42 | } 43 | 44 | radius_execution_context rec(radius); 45 | // Unfortunately for_each() is by value so needs to be wrapped in a lambda with side effects 46 | std::for_each(first, second, [&rec](auto& v) { 47 | rec(v); 48 | }); 49 | rec.finalize(); 50 | double V = CGAL::to_double(CGAL::Polygon_mesh_processing::volume(rec.polyhedron_exterior)); 51 | 52 | std::cout << "Calculated for R=" << radius << " V=" << V << std::endl; 53 | 54 | cache_.insert(it, { radius , V }); 55 | return V; 56 | } 57 | 58 | template 59 | std::string binary_search(It first, It second, std::pair range) { 60 | std::string abc[3]; 61 | std::tie(abc[0], abc[2]) = range; 62 | 63 | std::cout << "Testing " << abc[0] << " and " << abc[2] << std::endl; 64 | 65 | auto a_vol = initialize_radius_context_and_get_volume_with_cache(first, second, abc[0]); 66 | auto b_vol = initialize_radius_context_and_get_volume_with_cache(first, second, abc[2]); 67 | 68 | if (a_vol * 1.1 < b_vol) { 69 | double a = boost::lexical_cast(abc[0]); 70 | double c = boost::lexical_cast(abc[2]); 71 | auto b = (a + c) / 2.; 72 | 73 | abc[1] = boost::lexical_cast(b); 74 | 75 | if ((c - a) < 1.e-4) { 76 | std::cout << "Terminating search at " << abc[0] << " and " << abc[2] << std::endl; 77 | return boost::lexical_cast((a + c) / 2.); 78 | } 79 | 80 | for (int i = 1; i >= 0; --i) { 81 | auto r = binary_search(first, second, { abc[i], abc[i + 1] }); 82 | if (r != abc[i + 1]) { 83 | return r; 84 | } 85 | } 86 | } 87 | 88 | return abc[2]; 89 | } 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /radius_execution_context.cpp: -------------------------------------------------------------------------------- 1 | #include "radius_execution_context.h" 2 | #include "writer.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | static bool ENSURE_2ND_OP_NARROWER = true; 27 | 28 | namespace { 29 | template 30 | struct average_vector { 31 | CGAL::Vector_3 accum; 32 | size_t n = 0; 33 | 34 | void add(const CGAL::Vector_3& p) { 35 | accum += p; 36 | n += 1; 37 | } 38 | 39 | operator CGAL::Vector_3() const { 40 | return accum / n; 41 | } 42 | }; 43 | 44 | template 45 | struct average_point { 46 | average_vector accum; 47 | 48 | void add(const CGAL::Point_3& p) { 49 | accum.add(p - CGAL::ORIGIN); 50 | } 51 | 52 | operator CGAL::Point_3() const { 53 | return CGAL::ORIGIN + (CGAL::Vector_3) accum; 54 | } 55 | }; 56 | 57 | template 58 | void compute_vertex_normals(const non_manifold_polyhedron& input, std::vector>& result) { 59 | 60 | std::vector> face_normals; 61 | face_normals.reserve(input.indices.size()); 62 | 63 | K traits; 64 | 65 | 66 | for (auto& idxs : input.indices) { 67 | const auto& p0 = input.points[idxs[0]]; 68 | const auto& p1 = input.points[idxs[1]]; 69 | const auto& p2 = input.points[idxs[2]]; 70 | 71 | // https://github.com/CGAL/cgal/blob/1442c769c72f552bbd284733760958982e300cf7/ 72 | // Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/compute_normal.h#L69 73 | auto n = traits.construct_cross_product_vector_3_object()( 74 | traits.construct_vector_3_object()(p1, p2), 75 | traits.construct_vector_3_object()(p1, p0)); 76 | 77 | face_normals.push_back( 78 | traits.construct_scaled_vector_3_object()(n, typename K::FT(1) / typename K::FT(2)) 79 | ); 80 | } 81 | 82 | std::vector> facets_per_vertex(input.points.size()); 83 | for (auto it = input.indices.begin(); it != input.indices.end(); ++it) { 84 | for (auto& i : *it) { 85 | facets_per_vertex[i].push_back(std::distance(input.indices.begin(), it)); 86 | } 87 | } 88 | 89 | for (auto& idxs : facets_per_vertex) { 90 | // @todo there is no point in using average_vector as we normalize anyway 91 | average_vector accum; 92 | for (auto& i : idxs) { 93 | accum.add(face_normals[i]); 94 | } 95 | CGAL::Vector_3 v = accum; 96 | const typename K::FT norm = CGAL::approximate_sqrt(traits.compute_squared_length_3_object()(v)); 97 | if (norm != typename K::FT(0)) 98 | { 99 | v = traits.construct_divided_vector_3_object()(v, norm); 100 | } 101 | result.push_back(v); 102 | } 103 | } 104 | 105 | // @todo this should not be based on euclidean distance, but rather distance over edge/face as it will now close holes which is not the intention 106 | template 107 | bool cluster_vertices(non_manifold_polyhedron& s, non_manifold_polyhedron& result, double r) { 108 | typedef CGAL::Point_3 P; 109 | 110 | auto& points = s.points; 111 | auto& indices = s.indices; 112 | 113 | std::map clusters; 114 | boost::associative_property_map> clusters_map(clusters); 115 | 116 | std::cout << "Clustering with radius " << r << std::endl; 117 | 118 | int n = CGAL::cluster_point_set(points, clusters_map, CGAL::parameters::neighbor_radius(r)); 119 | std::cout << n << " clusters" << std::endl; 120 | 121 | std::vector> new_points_accum(n); 122 | for (auto& p : clusters) { 123 | new_points_accum[p.second].add(p.first); 124 | } 125 | 126 | std::vector

new_points; 127 | for (auto& p : new_points_accum) { 128 | new_points.push_back(p); 129 | } 130 | 131 | std::vector> new_indices; 132 | 133 | for (auto& x : indices) { 134 | std::vector transformed; 135 | std::transform(x.begin(), x.end(), std::back_inserter(transformed), [&clusters, &points](size_t i) { 136 | return clusters.find(points[i])->second; 137 | }); 138 | std::set transformed_unique(transformed.begin(), transformed.end()); 139 | if (transformed_unique.size() == transformed.size()) { 140 | new_indices.push_back(transformed); 141 | } 142 | } 143 | 144 | std::map, size_t> triangle_use; 145 | for (auto& x : new_indices) { 146 | std::set s(x.begin(), x.end()); 147 | triangle_use[s] ++; 148 | } 149 | 150 | auto it = new_indices.end(); 151 | while (it > new_indices.begin()) 152 | { 153 | it--; 154 | std::set s(it->begin(), it->end()); 155 | if (triangle_use[s] == 2) { 156 | it = new_indices.erase(it); 157 | std::cerr << "erasing" << std::endl; 158 | } 159 | } 160 | 161 | /* 162 | std::ofstream fs2("points.txt"); 163 | fs2 << "[\n"; 164 | for (auto& p : new_points) { 165 | fs2 << "[" << p.cartesian(0) << "," << p.cartesian(1) << "," << p.cartesian(2) << "],\n"; 166 | } 167 | fs2.seekp(-3, std::ios_base::cur); 168 | fs2 << "]"; 169 | fs2.close(); 170 | 171 | std::ofstream fs("debug.txt"); 172 | 173 | std::map, size_t> edge_use; 174 | auto add_use = [&edge_use](size_t a, size_t b) { 175 | if (a > b) std::swap(a, b); 176 | edge_use[{a, b}]++; 177 | }; 178 | fs << "[\n"; 179 | for (auto& tri : new_indices) { 180 | fs << "[" << tri[0] << "," << tri[1] << "," << tri[2] << "],\n"; 181 | add_use(tri[0], tri[1]); 182 | add_use(tri[1], tri[2]); 183 | add_use(tri[2], tri[0]); 184 | } 185 | fs.seekp(-3, std::ios_base::cur); 186 | fs << "]"; 187 | fs.close(); 188 | 189 | for (auto& p : edge_use) { 190 | if (p.second != 2) { 191 | return false; 192 | } 193 | } 194 | 195 | std::cerr << "removed points: " << << std::endl; 196 | */ 197 | 198 | CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup(new_points, new_indices); 199 | 200 | result = { new_points, new_indices }; 201 | 202 | /* 203 | auto v = CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(new_indices); 204 | std::cerr << "valid: " << v << std::endl; 205 | if (!v) { 206 | CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(new_indices); 207 | } 208 | 209 | T new_poly; 210 | CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(new_points, new_indices, new_poly); 211 | 212 | s = new_poly; 213 | */ 214 | 215 | return true; 216 | } 217 | 218 | // @todo this should not be based on euclidean distance, but rather distance over edge/face as it will now close holes which is not the intention 219 | template 220 | bool cluster_vertices(const CGAL::Polyhedron_3& s, non_manifold_polyhedron& result, double r) { 221 | typedef CGAL::Point_3 P; 222 | 223 | std::vector

points; 224 | std::vector> indices; 225 | CGAL::Polygon_mesh_processing::polygon_mesh_to_polygon_soup(s, points, indices); 226 | 227 | non_manifold_polyhedron nmp{ points, indices }; 228 | return cluster_vertices(nmp, result, r); 229 | } 230 | } 231 | 232 | radius_execution_context::radius_execution_context(const std::string& r, radius_settings rs) 233 | : settings_(rs) 234 | , radius_str(r) 235 | , radius(boost::lexical_cast(r)) 236 | , minkowski_triangles_(rs.get(radius_settings::MINKOWSKI_TRIANGLES)) 237 | , no_erosion_(rs.get(radius_settings::NO_EROSION)) 238 | , empty_(false) // no longer relevant, bug fixed 239 | , narrower_(ENSURE_2ND_OP_NARROWER && rs.get(radius_settings::NARROWER)) 240 | {} 241 | 242 | CGAL::Nef_polyhedron_3 radius_execution_context::construct_padding_volume_(const boost::optional& R) { 243 | double radius = R.get_value_or(this->radius); 244 | 245 | if (settings_.get(radius_settings::SPHERE)) { 246 | cgal_shape_t ico; 247 | 248 | CGAL::make_icosahedron(ico, cgal_point_t(0, 0, 0), 1.0); 249 | 250 | double ml = std::numeric_limits::infinity(); 251 | 252 | // Take the edge centers and find minimal distance from origin. 253 | // Or use vertex position 254 | for (auto e : edges(ico)) { 255 | auto v1 = e.halfedge()->vertex(); 256 | auto v2 = e.opposite().halfedge()->vertex(); 257 | 258 | double v1x = CGAL::to_double(v1->point().cartesian(0)); 259 | double v1y = CGAL::to_double(v1->point().cartesian(1)); 260 | double v1z = CGAL::to_double(v1->point().cartesian(2)); 261 | 262 | double v2x = CGAL::to_double(v2->point().cartesian(0)); 263 | double v2y = CGAL::to_double(v2->point().cartesian(1)); 264 | double v2z = CGAL::to_double(v2->point().cartesian(2)); 265 | 266 | #ifdef ICO_EDGE_CENTRES 267 | double vx = (v1x + v2x) / 2.; 268 | double vy = (v1y + v2y) / 2.; 269 | double vz = (v1z + v2z) / 2.; 270 | 271 | double l = std::sqrt(vx*vx + vy * vy + vz * vz); 272 | if (l < ml) { 273 | ml = l; 274 | } 275 | #else 276 | double l = std::sqrt(v1x*v1x + v1y * v1y + v1z * v1z); 277 | if (l < ml) { 278 | ml = l; 279 | } 280 | l = std::sqrt(v2x*v2x + v2y * v2y + v2z * v2z); 281 | if (l < ml) { 282 | ml = l; 283 | } 284 | #endif 285 | } 286 | 287 | // Divide the coordinates with the miminal distance 288 | for (auto& v : vertices(ico)) { 289 | v->point() = CGAL::ORIGIN + ((v->point() - CGAL::ORIGIN) * (radius / ml)); 290 | } 291 | 292 | ico_edge_length = 10.; 293 | // Now compute ico edge length, we use it later as a treshold for simplification 294 | for (auto e : edges(ico)) { 295 | auto v1 = e.halfedge()->vertex(); 296 | auto v2 = e.opposite().halfedge()->vertex(); 297 | 298 | double v1x = CGAL::to_double(v1->point().cartesian(0)); 299 | double v1y = CGAL::to_double(v1->point().cartesian(1)); 300 | double v1z = CGAL::to_double(v1->point().cartesian(2)); 301 | 302 | double v2x = CGAL::to_double(v2->point().cartesian(0)); 303 | double v2y = CGAL::to_double(v2->point().cartesian(1)); 304 | double v2z = CGAL::to_double(v2->point().cartesian(2)); 305 | 306 | double vx = (v1x - v2x); 307 | double vy = (v1y - v2y); 308 | double vz = (v1z - v2z); 309 | 310 | double l = std::sqrt(vx*vx + vy * vy + vz * vz); 311 | if (l < ico_edge_length) { 312 | ico_edge_length = l; 313 | } 314 | } 315 | 316 | return ifcopenshell::geometry::utils::create_nef_polyhedron(ico); 317 | } 318 | else { 319 | auto polycube = ifcopenshell::geometry::utils::create_cube(radius); 320 | return ifcopenshell::geometry::utils::create_nef_polyhedron(polycube); 321 | } 322 | } 323 | 324 | #include 325 | #include 326 | #include 327 | #include 328 | #include 329 | #include 330 | 331 | namespace SMS = CGAL::Surface_mesh_simplification; 332 | 333 | #ifdef _MSC_VER 334 | #include "windows.h" 335 | #include "psapi.h" 336 | void print_mem_usage() { 337 | PROCESS_MEMORY_COUNTERS_EX pmc; 338 | GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)); 339 | std::cout << "memory: " << pmc.PrivateUsage << std::endl; 340 | } 341 | #else 342 | void print_mem_usage() {} 343 | #endif 344 | 345 | 346 | namespace { 347 | template 348 | class queue_shortener { 349 | T t; 350 | size_t x; 351 | 352 | public: 353 | queue_shortener() : x(0) {} 354 | 355 | void add_polyhedron(const CGAL::Nef_polyhedron_3& u) { 356 | t.add_polyhedron(u); 357 | ++x; 358 | // does not seem to reduce memory footprint 359 | #if 0 360 | 361 | if (x == N) { 362 | std::cout << "before" << std::endl; 363 | print_mem_usage(); 364 | auto v = t.get_union(); 365 | v.extract_regularization(); 366 | t = T(); 367 | t.add_polyhedron(v); 368 | x = 0; 369 | std::cout << "after" << std::endl; 370 | print_mem_usage(); 371 | } 372 | #endif 373 | } 374 | 375 | CGAL::Nef_polyhedron_3 get_union() { 376 | return t.get_union(); 377 | } 378 | }; 379 | 380 | template 381 | void minkowski_sum_triangles_array_impl(std::vector, 3>>& triangles, CGAL::Nef_polyhedron_3& padding_volume, CGAL::Nef_polyhedron_3& result) { 382 | // queue_shortener<30, CGAL::Nef_nary_union_3< CGAL::Nef_polyhedron_3 > > accum; 383 | CGAL::Nef_nary_union_3< CGAL::Nef_polyhedron_3 > accum; 384 | for (auto& points : triangles) { 385 | double A = std::sqrt(CGAL::to_double(CGAL::Triangle_3(points[0], points[1], points[2]).squared_area())); 386 | if (A < (1.e-5 * 1.e-5 * 0.5)) { 387 | continue; 388 | } 389 | 390 | cgal_shape_t T; 391 | CGAL::Cartesian_converter C; 392 | T.make_triangle(C(points[0]), C(points[1]), C(points[2])); 393 | 394 | CGAL::Nef_polyhedron_3 Tnef(T); 395 | 396 | CGAL::Nef_polyhedron_3 padded = CGAL::minkowski_sum_3(Tnef, padding_volume); 397 | accum.add_polyhedron(padded); 398 | } 399 | result = accum.get_union(); 400 | } 401 | 402 | template 403 | void minkowski_sum_triangles_single_threaded(typename Poly::Facet_const_iterator begin, typename Poly::Facet_const_iterator end, CGAL::Nef_polyhedron_3& padding_volume, CGAL::Nef_polyhedron_3& result) { 404 | // queue_shortener<30, CGAL::Nef_nary_union_3< CGAL::Nef_polyhedron_3 > > accum; 405 | CGAL::Nef_nary_union_3< CGAL::Nef_polyhedron_3 > accum; 406 | 407 | size_t num = std::distance(begin, end); 408 | 409 | std::cout << "\n"; 410 | 411 | for (auto face = begin; face != end; ++face) { 412 | 413 | if (!face->is_triangle()) { 414 | std::cout << "Warning: non-triangular face!" << std::endl; 415 | continue; 416 | } 417 | 418 | typename Poly::Halfedge_around_facet_const_circulator current_halfedge = face->facet_begin(); 419 | CGAL::Point_3 points[3]; 420 | 421 | int i = 0; 422 | do { 423 | points[i] = current_halfedge->vertex()->point(); 424 | ++i; 425 | ++current_halfedge; 426 | } while (current_halfedge != face->facet_begin()); 427 | 428 | double A = std::sqrt(CGAL::to_double(CGAL::Triangle_3(points[0], points[1], points[2]).squared_area())); 429 | if (A < (1.e-5 * 1.e-5 * 0.5)) { 430 | std::cout << "Skipping triangle with area " << A << std::endl; 431 | continue; 432 | } 433 | 434 | cgal_shape_t T; 435 | CGAL::Cartesian_converter C; 436 | T.make_triangle(C(points[0]), C(points[1]), C(points[2])); 437 | 438 | CGAL::Nef_polyhedron_3 Tnef(T); 439 | 440 | CGAL::Nef_polyhedron_3 padded = CGAL::minkowski_sum_3(Tnef, padding_volume); 441 | accum.add_polyhedron(padded); 442 | 443 | auto n = std::distance(begin, face); 444 | if (n % 100) { 445 | std::cout << "\r" << (n * 100 / num) << "%"; 446 | std::cout.flush(); 447 | } 448 | } 449 | 450 | std::cout << "\n"; 451 | 452 | result = accum.get_union(); 453 | } 454 | 455 | void minkowski_sum_triangles_double_multithreaded(const CGAL::Polyhedron_3& poly_triangulated_epick, CGAL::Nef_polyhedron_3& padding_volume, CGAL::Nef_polyhedron_3& result) { 456 | // We need to copy to non-filtered kernel for multi threading 457 | // We're using double so that we can sneak in SMS, it would fail otherwise on missing sqrt() 458 | typedef CGAL::Simple_cartesian TriangleKernel; 459 | CGAL::Polyhedron_3 poly_triangulated; 460 | util::copy::polyhedron(poly_triangulated, poly_triangulated_epick); 461 | 462 | double stop_ratio = 0.1; 463 | CGAL::Surface_mesh_simplification::Edge_length_stop_predicate stop(1.e-3); 464 | int r = SMS::edge_collapse(poly_triangulated, stop, 465 | CGAL::parameters::vertex_index_map(get(CGAL::vertex_external_index, poly_triangulated)) 466 | .halfedge_index_map(get(CGAL::halfedge_external_index, poly_triangulated)) 467 | .get_cost(SMS::Edge_length_cost())); 468 | 469 | std::cout << "Removed " << r << " edges" << std::endl; 470 | 471 | size_t n_threads = std::thread::hardware_concurrency(); 472 | size_t n_facets = poly_triangulated.size_of_facets(); 473 | size_t facets_per_thread = n_facets / n_threads; 474 | 475 | std::vector< std::future > threadpool; 476 | std::vector< CGAL::Nef_polyhedron_3 > results(n_threads); 477 | std::vector< CGAL::Nef_polyhedron_3 > cubes(n_threads); 478 | 479 | for (size_t i = 0; i < n_threads; ++i) { 480 | // even the cubes have to be reconstructed to avoid race conditions 481 | auto polycube = ifcopenshell::geometry::utils::create_cube(0.05); 482 | cubes[i] = ifcopenshell::geometry::utils::create_nef_polyhedron(polycube); 483 | } 484 | 485 | std::vector< std::vector, 3>> > triangles(n_threads); 486 | threadpool.reserve(n_threads); 487 | 488 | CGAL::Polyhedron_3::Facet_const_iterator begin, end; 489 | begin = poly_triangulated.facets_begin(); 490 | 491 | for (size_t i = 0; i < n_threads; ++i) { 492 | if (i == (n_threads - 1)) { 493 | end = poly_triangulated.facets_end(); 494 | } else { 495 | end = begin; 496 | std::advance(end, facets_per_thread); 497 | } 498 | 499 | for (auto it = begin; it != end; ++it) { 500 | triangles[i].emplace_back(); 501 | CGAL::Polyhedron_3::Halfedge_around_facet_const_circulator current_halfedge = it->facet_begin(); 502 | int ii = 0; 503 | do { 504 | const auto& p = current_halfedge->vertex()->point(); 505 | /* 506 | CGAL::Gmpq px = p.cartesian(0).mpq(); 507 | CGAL::Gmpq py = p.cartesian(1).mpq(); 508 | CGAL::Gmpq pz = p.cartesian(2).mpq(); 509 | */ 510 | 511 | double px = p.cartesian(0); 512 | double py = p.cartesian(1); 513 | double pz = p.cartesian(2); 514 | 515 | triangles[i].back()[ii] = CGAL::Point_3(px, py, pz); 516 | ++ii; 517 | ++current_halfedge; 518 | } while (current_halfedge != it->facet_begin()); 519 | } 520 | 521 | std::future fu = std::async( 522 | std::launch::async, 523 | minkowski_sum_triangles_array_impl, 524 | std::ref(triangles[i]), 525 | std::ref(cubes[i]), 526 | std::ref(results[i]) 527 | ); 528 | threadpool.emplace_back(std::move(fu)); 529 | end = begin; 530 | } 531 | 532 | for (std::future& fu : threadpool) { 533 | fu.get(); 534 | } 535 | 536 | CGAL::Nef_nary_union_3< CGAL::Nef_polyhedron_3 > thread_join; 537 | for (auto& r : results) { 538 | thread_join.add_polyhedron(r); 539 | } 540 | 541 | result = thread_join.get_union(); 542 | } 543 | } 544 | 545 | 546 | // Create a bounding box (six-faced Nef poly) around a CGAL Polyhedron 547 | CGAL::Nef_polyhedron_3 create_bounding_box(const cgal_shape_t & input, double radius) { 548 | // @todo we can probably use 549 | // CGAL::Nef_polyhedron_3 nef(CGAL::Nef_polyhedron_3::COMPLETE) 550 | // Implementation detail: there is always an implicit box around the Nef, even when open or closed. 551 | // Never mind: The Minkowski sum cannot operate on unbounded inputs... 552 | 553 | // Create the complement of the Nef by subtracting from its bounding box, 554 | // see: https://github.com/tudelft3d/ifc2citygml/blob/master/off2citygml/Minkowski.cpp#L23 555 | auto bounding_box = CGAL::Polygon_mesh_processing::bbox(input); 556 | Kernel_::Point_3 bbmin(bounding_box.xmin(), bounding_box.ymin(), bounding_box.zmin()); 557 | Kernel_::Point_3 bbmax(bounding_box.xmax(), bounding_box.ymax(), bounding_box.zmax()); 558 | Kernel_::Vector_3 d(radius, radius, radius); 559 | bbmin = CGAL::ORIGIN + ((bbmin - CGAL::ORIGIN) - d); 560 | bbmax = CGAL::ORIGIN + ((bbmax - CGAL::ORIGIN) + d); 561 | cgal_shape_t poly_box = ifcopenshell::geometry::utils::create_cube(bbmin, bbmax); 562 | return ifcopenshell::geometry::utils::create_nef_polyhedron(poly_box); 563 | } 564 | 565 | 566 | class process_shape_item { 567 | double radius; 568 | bool minkowski_triangles_, threaded_; 569 | public: 570 | 571 | process_shape_item(double r, bool mintri, bool threaded) 572 | : radius(r) 573 | , minkowski_triangles_(mintri) 574 | , threaded_(threaded) 575 | {} 576 | 577 | #if 1 578 | void operator()(shape_callback_item* item_ptr, CGAL::Nef_polyhedron_3* result_ptr, CGAL::Nef_polyhedron_3* padding_volume_, CGAL::Nef_polyhedron_3* padding_volume_2_) { 579 | auto& item = *item_ptr; 580 | auto& result = *result_ptr; 581 | auto& padding_volume = *padding_volume_; 582 | auto& padding_volume_2 = *padding_volume_2_; 583 | #else 584 | void operator()(shape_callback_item item, CGAL::Nef_polyhedron_3& result) { 585 | #endif 586 | 587 | CGAL::Polyhedron_3 poly_triangulated; 588 | util::copy::polyhedron(poly_triangulated, item.polyhedron); 589 | if (!CGAL::Polygon_mesh_processing::triangulate_faces(poly_triangulated)) { 590 | std::cerr << "unable to triangulate all faces" << std::endl; 591 | return; 592 | } 593 | 594 | std::vector< 595 | std::pair< 596 | boost::graph_traits>::face_descriptor, 597 | boost::graph_traits>::face_descriptor>> self_intersections; 598 | CGAL::Polygon_mesh_processing::self_intersections(poly_triangulated, std::back_inserter(self_intersections)); 599 | 600 | CGAL::Nef_polyhedron_3 item_nef; 601 | bool item_nef_succeeded = false; 602 | if (self_intersections.empty()) { 603 | if (!(item_nef_succeeded = item.to_nef_polyhedron(item_nef, threaded_))) { 604 | std::cerr << "no nef for product" << std::endl; 605 | } 606 | } 607 | else { 608 | std::cerr << "self intersections, not trying to convert to Nef" << std::endl; 609 | } 610 | 611 | bool result_set = false; 612 | bool failed = false; 613 | 614 | if (!(minkowski_triangles_ || !item_nef_succeeded || !self_intersections.empty())) { 615 | item_nef.transform(item.transformation); 616 | 617 | auto T0 = timer::measure("minkowski_sum"); 618 | try { 619 | CGAL::Nef_polyhedron_3* item_nef_copy = new CGAL::Nef_polyhedron_3(item_nef); 620 | result = CGAL::minkowski_sum_3(*item_nef_copy, padding_volume); 621 | // So this is funky, we got segfaults in the destructor when exceptions were 622 | // raised, so we only delete when minkowski (actually the convex_decomposition) 623 | // succeed. Otherwise, we just have to incur some memory leak. 624 | // @todo report this to cgal. 625 | // Still an issue on 5.2. valgrind reports an error as well. 626 | 627 | delete item_nef_copy; 628 | result_set = true; 629 | } catch (CGAL::Failure_exception&) { 630 | failed = true; 631 | std::cerr << "Minkowski on volume failed, retrying with individual triangles" << std::endl; 632 | } 633 | T0.stop(); 634 | } 635 | 636 | double max_triangle_area = 0.; 637 | 638 | for (auto &face : faces(poly_triangulated)) { 639 | 640 | if (!face->is_triangle()) { 641 | std::cout << "Warning: non-triangular face!" << std::endl; 642 | continue; 643 | } 644 | 645 | CGAL::Polyhedron_3::Halfedge_around_facet_const_circulator current_halfedge = face->facet_begin(); 646 | CGAL::Point_3 points[3]; 647 | 648 | int i = 0; 649 | do { 650 | points[i] = current_halfedge->vertex()->point(); 651 | ++i; 652 | ++current_halfedge; 653 | } while (current_halfedge != face->facet_begin()); 654 | 655 | double A = std::sqrt(CGAL::to_double(CGAL::Triangle_3(points[0], points[1], points[2]).squared_area())); 656 | 657 | if (A > max_triangle_area) { 658 | max_triangle_area = A; 659 | } 660 | } 661 | 662 | if (!result_set && (poly_triangulated.size_of_facets() > 1000 || max_triangle_area < 1.e-5)) { 663 | 664 | if (poly_triangulated.size_of_facets() > 1000) { 665 | std::cerr << "Too many individual triangles, using bounding box" << std::endl; 666 | } 667 | else { 668 | std::cerr << "Max triangle area is " << max_triangle_area << ", using bounding box" << std::endl; 669 | } 670 | auto bb = CGAL::Polygon_mesh_processing::bbox(item.polyhedron); 671 | cgal_point_t lower(bb.min(0) - radius, bb.min(1) - radius, bb.min(2) - radius); 672 | cgal_point_t upper(bb.max(0) + radius, bb.max(1) + radius, bb.max(2) + radius); 673 | auto bbpl = ifcopenshell::geometry::utils::create_cube(lower, upper); 674 | result = ifcopenshell::geometry::utils::create_nef_polyhedron(bbpl); 675 | 676 | } 677 | else if (!result_set) { 678 | 679 | auto T2 = timer::measure("self_intersection_handling"); 680 | 681 | if (self_intersections.size()) { 682 | std::cerr << self_intersections.size() << " self-intersections for product" << std::endl; 683 | } 684 | 685 | minkowski_sum_triangles_single_threaded>( 686 | poly_triangulated.facets_begin(), 687 | poly_triangulated.facets_end(), 688 | padding_volume_2, result 689 | ); 690 | result.transform(item.transformation); 691 | 692 | T2.stop(); 693 | } 694 | 695 | auto T1 = timer::measure("opening_handling"); 696 | if (item.wall_direction && item.openings.size()) { 697 | static const Eigen::Vector3d Zax(0, 0, 1); 698 | // @todo derive from model. 699 | // @todo since many walls will be parallel we can cache these polyhedrons 700 | static const double EPS = 1.e-5; 701 | 702 | auto Yax = Zax.cross(*item.wall_direction).normalized(); 703 | 704 | auto x0 = *item.wall_direction * -radius; 705 | auto x1 = *item.wall_direction * +radius; 706 | 707 | auto y0 = Yax * -(radius + EPS); 708 | auto y1 = Yax * +(radius + EPS); 709 | 710 | Kernel_::Point_3 X0(x0(0), x0(1), x0(2)); 711 | Kernel_::Point_3 X1(x1(0), x1(1), x1(2)); 712 | 713 | Kernel_::Point_3 Y0(y0(0), y0(1), y0(2)); 714 | Kernel_::Point_3 Y1(y1(0), y1(1), y1(2)); 715 | 716 | Kernel_::Point_3 Z0(0, 0, -radius); 717 | Kernel_::Point_3 Z1(0, 0, +radius); 718 | 719 | // CGAL::Nef_polyhedron_3 X(CGAL::Segment_3(X0, X1)); 720 | CGAL::Nef_polyhedron_3 Y(CGAL::Segment_3(Y0, Y1)); 721 | // CGAL::Nef_polyhedron_3 Z(CGAL::Segment_3(Z0, Z1)); 722 | // auto ZX = CGAL::minkowski_sum_3(X, Z); 723 | 724 | // manual minkowski sum... 725 | CGAL::Polyhedron_3 zx; 726 | std::list zx_points{ { 727 | CGAL::ORIGIN + ((X0 - CGAL::ORIGIN) + (Z0 - CGAL::ORIGIN)), 728 | CGAL::ORIGIN + ((X1 - CGAL::ORIGIN) + (Z0 - CGAL::ORIGIN)), 729 | CGAL::ORIGIN + ((X1 - CGAL::ORIGIN) + (Z1 - CGAL::ORIGIN)), 730 | CGAL::ORIGIN + ((X0 - CGAL::ORIGIN) + (Z1 - CGAL::ORIGIN)) 731 | } }; 732 | std::vector> zx_idxs{ {{{0,1,2,3}}} }; 733 | util::PolyFromMesh m(zx_points, zx_idxs); 734 | zx.delegate(m); 735 | CGAL::Nef_polyhedron_3 ZX(zx); 736 | 737 | CGAL::Nef_nary_union_3< CGAL::Nef_polyhedron_3 > opening_union; 738 | for (auto& op : item.openings) { 739 | 740 | const auto& xdir = *item.wall_direction; 741 | double min_dot = +std::numeric_limits::infinity(); 742 | double max_dot = -std::numeric_limits::infinity(); 743 | double min_z = +std::numeric_limits::infinity(); 744 | double max_z = -std::numeric_limits::infinity(); 745 | 746 | for (const auto& v : vertices(op->polyhedron)) { 747 | auto p = v->point(); 748 | p = p.transform(op->transformation); 749 | Eigen::Vector3d vv( 750 | CGAL::to_double(p.cartesian(0)), 751 | CGAL::to_double(p.cartesian(1)), 752 | CGAL::to_double(p.cartesian(2)) 753 | ); 754 | double d = xdir.dot(vv); 755 | if (d < min_dot) { 756 | min_dot = d; 757 | } 758 | if (d > max_dot) { 759 | max_dot = d; 760 | } 761 | if (vv.z() < min_z) { 762 | min_z = vv.z(); 763 | } 764 | if (vv.z() > max_z) { 765 | max_z = vv.z(); 766 | } 767 | } 768 | 769 | // These are basically workarounds for a bug in 770 | // nary_union which is also used on minkowski of concave operands. 771 | // It segaults on getting front() of an empty queue. 772 | // with the patch https://patch-diff.githubusercontent.com/raw/CGAL/cgal/pull/4768.patch 773 | // (applied now by the IfcOpenShell build script) 774 | // these fixes are not necessary anymore. 775 | if ((max_dot - min_dot) < radius * 2) { 776 | std::cerr << "Opening too narrow to have effect after incorporating radius, skipping" << std::endl; 777 | continue; 778 | } 779 | 780 | if ((max_z - min_z) < radius * 2) { 781 | std::cerr << "Opening too narrow to have effect after incorporating radius, skipping" << std::endl; 782 | continue; 783 | } 784 | 785 | auto bounds = create_bounding_box(op->polyhedron, radius); 786 | CGAL::Nef_polyhedron_3 opening_nef; 787 | if (!op->to_nef_polyhedron(opening_nef, threaded_)) { 788 | std::cerr << "no nef for opening" << std::endl; 789 | continue; 790 | } 791 | opening_nef.transform(op->transformation); 792 | bounds.transform(op->transformation); 793 | 794 | auto temp = bounds - opening_nef; 795 | temp = CGAL::minkowski_sum_3(ZX, temp); 796 | temp = bounds - temp; 797 | temp = CGAL::minkowski_sum_3(temp, Y); 798 | 799 | #ifdef GEOBIM_DEBUG 800 | simple_obj_writer x("opening-" + op->id); 801 | x(nullptr, item.polyhedron.facets_begin(), item.polyhedron.facets_end()); 802 | x(nullptr, op->polyhedron.facets_begin(), op->polyhedron.facets_end()); 803 | CGAL::Polyhedron_3 temp2; 804 | temp.convert_to_polyhedron(temp2); 805 | x(nullptr, temp2.facets_begin(), temp2.facets_end()); 806 | #endif 807 | 808 | result -= temp; 809 | } 810 | } 811 | 812 | result.extract_regularization(); 813 | 814 | T1.stop(); 815 | } 816 | }; 817 | 818 | void radius_execution_context::operator()(shape_callback_item* item) { 819 | auto it = first_product_for_geom_id.find(item->geom_reference); 820 | if (it != first_product_for_geom_id.end()) { 821 | if (it->second != item->src) { 822 | if (reused_products.find(item->src) == reused_products.end()) { 823 | std::cout << "Reused " << it->second << std::endl; 824 | reused_products.insert({ item->src, { 825 | it->second, 826 | placements.find(it->second)->second, 827 | item->transformation} 828 | }); 829 | } 830 | return; 831 | } 832 | } 833 | else { 834 | first_product_for_geom_id.insert(it, { item->geom_reference, item->src }); 835 | // placements only need to be inserted once. 836 | placements[item->src] = item->transformation.inverse(); 837 | } 838 | 839 | product_geometries[item->src].emplace_back(); 840 | auto result_nef = &product_geometries[item->src].back(); 841 | process_shape_item* task = new process_shape_item(radius, minkowski_triangles_, (bool) threads_); 842 | 843 | padding_volumes_.push_back({construct_padding_volume_(), construct_padding_volume_()}); 844 | auto& pp = padding_volumes_.back(); 845 | 846 | if (!threads_) { 847 | (*task)(item, result_nef, &pp.first, &pp.second); 848 | } else { 849 | /* 850 | bool placed = false; 851 | while (!placed) { 852 | for (auto& fu : threadpool_) { 853 | if (!fu.valid()) { 854 | fu = std::async(std::launch::async, std::ref(*task), item, result_nef); 855 | placed = true; 856 | break; 857 | } 858 | } 859 | if (!placed) { 860 | for (auto& fu : threadpool_) { 861 | if (fu.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { 862 | try { 863 | fu.get(); 864 | } 865 | catch (std::exception& e) { 866 | std::cerr << e.what() << std::endl; 867 | } 868 | catch (...) { 869 | std::cerr << "unkown error" << std::endl; 870 | } 871 | fu = std::async(std::launch::async, std::ref(*task), item, result_nef); 872 | placed = true; 873 | break; 874 | } 875 | } 876 | } 877 | } 878 | */ 879 | 880 | while (threadpool_.size() == threads_) { 881 | for (size_t i = 0; i < threadpool_.size(); ++i) { 882 | std::future &fu = threadpool_[i]; 883 | std::future_status status = fu.wait_for(std::chrono::seconds(0)); 884 | if (status == std::future_status::ready) { 885 | fu.get(); 886 | 887 | std::swap(threadpool_[i], threadpool_.back()); 888 | threadpool_.pop_back(); 889 | } 890 | } 891 | } 892 | 893 | std::future fu = std::async(std::launch::async, *task, item, result_nef, &pp.first, &pp.second); 894 | threadpool_.emplace_back(std::move(fu)); 895 | } 896 | 897 | } 898 | 899 | namespace { 900 | double bbox_diagonal(const CGAL::Bbox_3& b) { 901 | return std::sqrt( 902 | (b.xmax() - b.xmin()) * (b.xmax() - b.xmin()) + 903 | (b.ymax() - b.ymin()) * (b.ymax() - b.ymin()) + 904 | (b.zmax() - b.zmin()) * (b.zmax() - b.zmin()) 905 | ); 906 | } 907 | } 908 | 909 | 910 | namespace { 911 | // Return pair in map with largest value 912 | template 913 | std::pair map_max_value(const std::map& x) { 914 | return *std::max_element(x.begin(), x.end(), [](const std::pair& p1, const std::pair& p2) { 915 | return p1.second < p2.second; 916 | }); 917 | } 918 | 919 | template 920 | std::pair nth_largest_value_from_map(const std::map& x) { 921 | std::vector> vec; 922 | for (auto& y : x) { 923 | vec.push_back({ y.second, y.first }); 924 | } 925 | std::sort(vec.begin(), vec.end()); 926 | std::reverse(vec.begin(), vec.end()); 927 | if (N > vec.size()) { 928 | throw std::runtime_error("not enough components"); 929 | } 930 | auto it = vec.begin() + N; 931 | return { it->second, it->first }; 932 | } 933 | 934 | } 935 | 936 | // Extract the exterior component of a CGAL Polyhedron 937 | void radius_execution_context::extract_in_place(cgal_shape_t& input, extract_component component) const { 938 | if (input.facets_begin() == input.facets_end()) { 939 | throw std::runtime_error("Empty input operand to extract()"); 940 | } 941 | 942 | // split_connected_components() is introduced in CGAL 5 :( 943 | 944 | typedef boost::graph_traits::face_descriptor face_descriptor; 945 | typedef boost::graph_traits::vertex_descriptor vertex_descriptor; 946 | typedef boost::graph_traits::vertex_iterator vertex_iterator; 947 | 948 | boost::property_map::type fim 949 | = get(boost::face_external_index, input); 950 | 951 | boost::vector_property_map::type> 953 | fsm(fim); 954 | 955 | auto ffim = CGAL::Polygon_mesh_processing::parameters::face_index_map(fim); 956 | 957 | CGAL::Polygon_mesh_processing::connected_components( 958 | input, 959 | fsm, 960 | ffim); 961 | 962 | face_descriptor largest_component_facet; 963 | std::map component_sizes; 964 | std::map component_areas; 965 | 966 | BOOST_FOREACH(face_descriptor f, faces(input)) { 967 | auto idx = fsm[f]; 968 | component_sizes[idx] ++; 969 | component_areas[idx] += CGAL::to_double(CGAL::Polygon_mesh_processing::area(std::vector{f}, input)); 970 | if (component_sizes.rbegin()->first == idx) { 971 | largest_component_facet = f; 972 | } 973 | } 974 | 975 | for (auto& p : component_sizes) { 976 | std::cout << "component " << p.first << " has " << p.second << " and area " << component_areas[p.first] << std::endl; 977 | } 978 | 979 | typedef std::map Internal_vertex_map; 980 | typedef boost::associative_property_map Vertex_index_map; 981 | Internal_vertex_map internal_vertex_index_map; 982 | Vertex_index_map vertex_index_map(internal_vertex_index_map); 983 | vertex_iterator vb, ve; 984 | std::size_t counter = 0; 985 | for (boost::tie(vb, ve) = vertices(input); vb != ve; ++vb, ++counter) { 986 | put(vertex_index_map, *vb, counter); 987 | } 988 | 989 | std::vector components_to_keep; 990 | 991 | if (component == LARGEST_AREA) { 992 | components_to_keep.push_back(map_max_value(component_areas).first); 993 | } else if (component == SECOND_LARGEST_AREA) { 994 | components_to_keep.push_back(nth_largest_value_from_map<1>(component_areas).first); 995 | } 996 | 997 | CGAL::Polygon_mesh_processing::keep_connected_components( 998 | input, 999 | components_to_keep, 1000 | fsm, 1001 | CGAL::Polygon_mesh_processing::parameters::vertex_index_map(vertex_index_map)); 1002 | } 1003 | 1004 | cgal_shape_t radius_execution_context::extract(const cgal_shape_t& input, extract_component component) const { 1005 | auto input_copy = input; 1006 | extract_in_place(input_copy, component); 1007 | return input_copy; 1008 | } 1009 | 1010 | 1011 | #include 1012 | 1013 | void radius_execution_context::set_threads(size_t n) { 1014 | if (!threads_) { 1015 | threads_ = n; 1016 | // threadpool_.resize(n); 1017 | /* 1018 | padding_volumes_.resize(n); 1019 | padding_vol_ptr_.resize(n); 1020 | for (size_t i = 0; i < n; ++i) { 1021 | padding_volumes_[i] = std::make_pair(construct_padding_volume_(), construct_padding_volume_()); 1022 | padding_vol_ptr_[i] = &padding_volumes_[i]; 1023 | } 1024 | */ 1025 | } 1026 | } 1027 | 1028 | // Completes the boolean union, extracts exterior and erodes padding radius 1029 | void radius_execution_context::finalize() { 1030 | 1031 | CGAL::Nef_nary_union_3< CGAL::Nef_polyhedron_3 > union_collector; 1032 | 1033 | for (auto& fu : threadpool_) { 1034 | if (fu.valid()) { 1035 | try { 1036 | fu.get(); 1037 | } 1038 | catch (std::exception& e) { 1039 | std::cerr << e.what() << std::endl; 1040 | } 1041 | catch (...) { 1042 | std::cerr << "unkown error" << std::endl; 1043 | } 1044 | } 1045 | } 1046 | 1047 | auto T = timer::measure("nef_boolean_union"); 1048 | 1049 | for (auto& p : product_geometries) { 1050 | // @todo This part can still be multithreaded 1051 | if (p.second.size() > 1) { 1052 | CGAL::Nef_nary_union_3> per_product_collector; 1053 | for (auto& r : p.second) { 1054 | per_product_collector.add_polyhedron(r); 1055 | } 1056 | p.second = { per_product_collector.get_union() }; 1057 | // @todo is this necessary when using the n-ary op? 1058 | // p.second.front().extract_regularization(); 1059 | } 1060 | union_collector.add_polyhedron(p.second.front()); 1061 | } 1062 | 1063 | for (auto& p : reused_products) { 1064 | auto copy = product_geometries[p.second.target].front(); 1065 | copy.transform(p.second.inverse); 1066 | copy.transform(p.second.own); 1067 | union_collector.add_polyhedron(copy); 1068 | } 1069 | 1070 | // @todo spatial sorting? 1071 | auto boolean_result = union_collector.get_union(); 1072 | // boolean_result.extract_regularization(); 1073 | T.stop(); 1074 | 1075 | if (no_erosion_) { 1076 | exterior = boolean_result; 1077 | 1078 | if (exterior.is_simple()) { 1079 | polyhedron_exterior = ifcopenshell::geometry::utils::create_polyhedron(exterior); 1080 | } 1081 | else { 1082 | CGAL::convert_nef_polyhedron_to_polygon_mesh(exterior, polyhedron_exterior); 1083 | } 1084 | } else { 1085 | 1086 | auto T2 = timer::measure("result_nef_processing"); 1087 | polyhedron_exterior = ifcopenshell::geometry::utils::create_polyhedron(boolean_result); 1088 | 1089 | { 1090 | simple_obj_writer tmp_debug("debug-after-boolean"); 1091 | tmp_debug(nullptr, polyhedron_exterior.facets_begin(), polyhedron_exterior.facets_end()); 1092 | } 1093 | 1094 | // @todo Wasteful: remove interior on Nef? 1095 | extract_in_place(polyhedron_exterior, LARGEST_AREA); 1096 | 1097 | if (settings_.get(radius_settings::SPHERE)) { 1098 | typedef CGAL::Simple_cartesian TriangleKernel; 1099 | CGAL::Polyhedron_3 poly_simple; 1100 | 1101 | util::copy::polyhedron(poly_simple, polyhedron_exterior); 1102 | 1103 | CGAL::Polygon_mesh_processing::triangulate_faces(poly_simple); 1104 | 1105 | poly_simple.normalize_border(); 1106 | if (!poly_simple.is_valid(false, 1)) { 1107 | std::cerr << "invalid before clustering" << std::endl; 1108 | return; 1109 | } 1110 | 1111 | non_manifold_polyhedron poly_simple_2; 1112 | 1113 | if (!cluster_vertices(poly_simple, poly_simple_2, ico_edge_length / 2.)) { 1114 | return; 1115 | } 1116 | 1117 | /* 1118 | poly_simple.normalize_border(); 1119 | if (!poly_simple.is_valid(false, 1)) { 1120 | std::cerr << "invalid after clustering" << std::endl; 1121 | return; 1122 | } 1123 | 1124 | { 1125 | simple_obj_writer tmp_debug("debug-after-custering"); 1126 | tmp_debug(nullptr, poly_simple.facets_begin(), poly_simple.facets_end()); 1127 | } 1128 | 1129 | util::copy::polyhedron(polyhedron_exterior, poly_simple); 1130 | 1131 | polyhedron_exterior.normalize_border(); 1132 | if (!polyhedron_exterior.is_valid(false, 1)) { 1133 | std::cerr << "invalid after conversion" << std::endl; 1134 | return; 1135 | } 1136 | 1137 | */ 1138 | 1139 | /* 1140 | // SMS (again) does not really work, geometrical constraints not sattisfied 1141 | Stats stats; 1142 | My_visitor vis(&stats); 1143 | 1144 | // simplify as small detail create large normal artefacts 1145 | CGAL::Surface_mesh_simplification::Edge_length_stop_predicate stop(length_threshold * length_threshold); 1146 | int r = SMS::edge_collapse(poly_simple, stop, 1147 | CGAL::parameters::vertex_index_map(get(CGAL::vertex_external_index, poly_simple)) 1148 | .halfedge_index_map(get(CGAL::halfedge_external_index, poly_simple)) 1149 | .get_cost(SMS::Edge_length_cost()) 1150 | .get_placement(SMS::Midpoint_placement()) 1151 | .visitor(vis)); 1152 | 1153 | std::cout << "Removed " << r << " edges" << std::endl; 1154 | 1155 | 1156 | */ 1157 | 1158 | std::vector> vertex_normals_map; 1159 | compute_vertex_normals(poly_simple_2, vertex_normals_map); 1160 | 1161 | auto nit = vertex_normals_map.begin(); 1162 | for (auto it = poly_simple_2.points.begin(); it != poly_simple_2.points.end(); ++it, ++nit) { 1163 | *it -= *nit * radius; 1164 | } 1165 | 1166 | /* 1167 | // calculate normals 1168 | std::map vertex_normals; 1169 | boost::associative_property_map> vertex_normals_map(vertex_normals); 1170 | CGAL::Polygon_mesh_processing::compute_vertex_normals(poly_simple_2, vertex_normals_map); 1171 | 1172 | std::map face_normals; 1173 | boost::associative_property_map> face_normals_map(face_normals); 1174 | CGAL::Polygon_mesh_processing::compute_face_normals(poly_simple_2, face_normals_map); 1175 | 1176 | for (auto& v : vertices(polyhedron_exterior)) { 1177 | 1178 | typedef CGAL::Face_around_target_circulator face_around_target_circulator; 1179 | face_around_target_circulator circ(v->halfedge(), polyhedron_exterior); 1180 | auto done = circ; 1181 | CGAL::Vector_3 accum, vnorm; 1182 | size_t n = 0; 1183 | do { 1184 | 1185 | cgal_face_descriptor_t f = *circ; 1186 | std::list fs = { f }; 1187 | auto A = CGAL::Polygon_mesh_processing::area(fs, polyhedron_exterior); 1188 | if (A > 1.e-5) { 1189 | accum += face_normals[f]; 1190 | ++n; 1191 | } 1192 | ++circ; 1193 | } while (circ != done); 1194 | 1195 | // We don't just use vertex normals, but use connected facet normals if their size is above a certain threshold. 1196 | 1197 | // Let's try how it goes with the vertex clustering, we set n to zero. 1198 | n = 0; 1199 | 1200 | if (n) { 1201 | vnorm = accum / n; 1202 | } 1203 | else { 1204 | vnorm = vertex_normals[v]; 1205 | } 1206 | 1207 | std::cout << "N " << CGAL::to_double(vnorm.cartesian(0)) 1208 | << " " << CGAL::to_double(vnorm.cartesian(1)) 1209 | << " " << CGAL::to_double(vnorm.cartesian(2)) << std::endl; 1210 | 1211 | std::cout << "p0 " << CGAL::to_double(v->point().cartesian(0)) 1212 | << " " << CGAL::to_double(v->point().cartesian(1)) 1213 | << " " << CGAL::to_double(v->point().cartesian(2)) << std::endl; 1214 | 1215 | v->point() -= vnorm * radius; 1216 | 1217 | std::cout << "p1 " << CGAL::to_double(v->point().cartesian(0)) 1218 | << " " << CGAL::to_double(v->point().cartesian(1)) 1219 | << " " << CGAL::to_double(v->point().cartesian(2)) << std::endl; 1220 | } 1221 | */ 1222 | 1223 | /* 1224 | { 1225 | simple_obj_writer tmp_debug("debug-after-erosion"); 1226 | tmp_debug(nullptr, polyhedron_exterior.facets_begin(), polyhedron_exterior.facets_end()); 1227 | } 1228 | */ 1229 | 1230 | { 1231 | simple_obj_writer tmp_debug("debug-after-erosion"); 1232 | tmp_debug.point_lookup = &poly_simple_2.points; 1233 | tmp_debug(nullptr, poly_simple_2.indices.begin(), poly_simple_2.indices.end()); 1234 | } 1235 | 1236 | cluster_vertices(poly_simple_2, polyhedron_exterior_nm, ico_edge_length / 2.); 1237 | 1238 | /* 1239 | { 1240 | simple_obj_writer tmp_debug("debug-after-custering-again"); 1241 | tmp_debug(nullptr, polyhedron_exterior.facets_begin(), polyhedron_exterior.facets_end()); 1242 | } 1243 | */ 1244 | 1245 | { 1246 | simple_obj_writer tmp_debug("debug-after-custering-again"); 1247 | tmp_debug.point_lookup = &polyhedron_exterior_nm.points; 1248 | tmp_debug(nullptr, polyhedron_exterior_nm.indices.begin(), polyhedron_exterior_nm.indices.end()); 1249 | } 1250 | 1251 | // util::copy::polyhedron(polyhedron_exterior, poly_simple); 1252 | } 1253 | else { 1254 | 1255 | { 1256 | simple_obj_writer tmp_debug("debug-exterior"); 1257 | tmp_debug(nullptr, polyhedron_exterior.facets_begin(), polyhedron_exterior.facets_end()); 1258 | } 1259 | 1260 | #if 0 1261 | 1262 | exterior = ifcopenshell::geometry::utils::create_nef_polyhedron(polyhedron_exterior); 1263 | bounding_box = create_bounding_box(polyhedron_exterior); 1264 | 1265 | complement = bounding_box - exterior; 1266 | complement.extract_regularization(); 1267 | // @nb padding cube is potentially slightly larger to result in a thinner result 1268 | // then another radius for comparison. 1269 | complement_padded = complement; // CGAL::minkowski_sum_3(complement, padding_volume_2); 1270 | complement_padded.extract_regularization(); 1271 | T2.stop(); 1272 | 1273 | { 1274 | auto T = timer::measure("result_nef_to_poly"); 1275 | #if 0 1276 | // @todo I imagine this operation is costly, we can also convert the padded complement to 1277 | // polyhedron, and remove the connected component that belongs to the bbox, then reverse 1278 | // the remaining poly to point to the interior? 1279 | exterior -= complement_padded; 1280 | #else 1281 | // Rougly twice as fast as the complexity is half (box complexity is negligable). 1282 | 1283 | // Re above: extracting the interior shell did not prove to be reliable even with 1284 | // the undocumented function convert_inner_shell_to_polyhedron(). Therefore we 1285 | // subtract from the padded box as that will have lower complexity than above. 1286 | // Mark_bounded_volumes on the completement also did not work. 1287 | exterior = bounding_box - complement_padded; 1288 | #endif 1289 | T.stop(); 1290 | } 1291 | 1292 | exterior.extract_regularization(); 1293 | 1294 | if (exterior.is_simple()) { 1295 | auto T1 = timer::measure("result_nef_to_poly"); 1296 | polyhedron_exterior = ifcopenshell::geometry::utils::create_polyhedron(exterior); 1297 | T1.stop(); 1298 | 1299 | auto vol = CGAL::Polygon_mesh_processing::volume(polyhedron_exterior); 1300 | std::cout << "Volume with radius " << radius << " is " << vol << std::endl; 1301 | } 1302 | else { 1303 | CGAL::convert_nef_polyhedron_to_polygon_mesh(exterior, polyhedron_exterior); 1304 | std::cout << "Result with radius " << radius << " is not manifold" << std::endl; 1305 | } 1306 | 1307 | #else 1308 | 1309 | CGAL::Polyhedron_3 poly_triangulated; 1310 | util::copy::polyhedron(poly_triangulated, polyhedron_exterior); 1311 | if (!CGAL::Polygon_mesh_processing::triangulate_faces(poly_triangulated)) { 1312 | std::cerr << "unable to triangulate all faces" << std::endl; 1313 | return; 1314 | } 1315 | 1316 | auto padding_volume = construct_padding_volume_(narrower_ 1317 | ? (this->radius + 1e-7) // sightly higher so volume get's a little bit inset 1318 | : this->radius); 1319 | 1320 | minkowski_sum_triangles_single_threaded>( 1321 | poly_triangulated.facets_begin(), 1322 | poly_triangulated.facets_end(), 1323 | padding_volume, exterior 1324 | ); 1325 | 1326 | if (exterior.is_simple()) { 1327 | polyhedron_exterior = ifcopenshell::geometry::utils::create_polyhedron(exterior); 1328 | } 1329 | else { 1330 | CGAL::convert_nef_polyhedron_to_polygon_mesh(exterior, polyhedron_exterior); 1331 | } 1332 | 1333 | extract_in_place(polyhedron_exterior, SECOND_LARGEST_AREA); 1334 | #endif 1335 | } 1336 | 1337 | T2.stop(); 1338 | } 1339 | 1340 | std::cout << "exterior poly num facets: " << polyhedron_exterior.size_of_facets() << std::endl; 1341 | } 1342 | 1343 | -------------------------------------------------------------------------------- /radius_execution_context.h: -------------------------------------------------------------------------------- 1 | #ifndef RADIUS_EXECUTION_CONTEXT_H 2 | #define RADIUS_EXECUTION_CONTEXT_H 3 | 4 | #include "processing.h" 5 | #include "utils.h" 6 | 7 | #include 8 | 9 | template 10 | class lazy_nary_union { 11 | std::list a; 12 | CGAL::Nef_nary_union_3 b, empty; 13 | T c; 14 | int state = 0; 15 | 16 | public: 17 | void add_polyhedron(const T& t) { 18 | if (state == 0) { 19 | a.push_back(t); 20 | } else { 21 | throw std::runtime_error(""); 22 | } 23 | } 24 | 25 | const T& get_union() { 26 | if (state == 0) { 27 | ++state; 28 | for (auto& p : a) { 29 | b.add_polyhedron(p); 30 | } 31 | ++state; 32 | if (!a.empty()) { 33 | c = b.get_union(); 34 | } 35 | } 36 | return c; 37 | } 38 | 39 | void clear() { 40 | state = 0; 41 | a.clear(); 42 | b = empty; 43 | } 44 | }; 45 | 46 | #include 47 | 48 | struct radius_settings : std::bitset<4> { 49 | enum V { 50 | NARROWER, MINKOWSKI_TRIANGLES, NO_EROSION, SPHERE 51 | }; 52 | radius_settings& set(V v, bool b) { 53 | (*this)[(int) v] = b; 54 | return *this; 55 | } 56 | bool get(V v) const { 57 | return (*this)[(int) v]; 58 | } 59 | }; 60 | 61 | // State (polyhedra mostly) that are relevant only for one radius 62 | struct radius_execution_context : public execution_context { 63 | radius_settings settings_; 64 | std::string radius_str; 65 | double radius, ico_edge_length; 66 | CGAL::Nef_polyhedron_3 exterior; 67 | 68 | bool narrower_ = false; 69 | 70 | cgal_shape_t polyhedron_exterior; 71 | non_manifold_polyhedron> polyhedron_exterior_nm; 72 | 73 | enum extract_component { INTERIOR, EXTERIOR, LARGEST_AREA, SECOND_LARGEST_AREA }; 74 | bool minkowski_triangles_, no_erosion_, empty_; 75 | 76 | // @todo this sets ico_edge_length 77 | CGAL::Nef_polyhedron_3 construct_padding_volume_(const boost::optional& = boost::none); 78 | 79 | boost::optional threads_; 80 | 81 | radius_execution_context(const std::string& radius, radius_settings=radius_settings()); 82 | radius_execution_context(const radius_execution_context&) = delete; 83 | radius_execution_context& operator=(const radius_execution_context&) = delete; 84 | 85 | 86 | IfcUtil::IfcBaseEntity* previous_src = nullptr; 87 | std::string previous_geom_ref; 88 | // lazy_nary_union > per_product_collector; 89 | cgal_placement_t last_place; 90 | 91 | std::vector< std::future > threadpool_; 92 | std::list< std::pair, CGAL::Nef_polyhedron_3 > > padding_volumes_; 93 | // Extra indirection for easy swaps 94 | // Not used currently 95 | std::vector< std::pair, CGAL::Nef_polyhedron_3 >* > padding_vol_ptr_; 96 | 97 | void set_threads(size_t n); 98 | 99 | struct geometry_reference { 100 | IfcUtil::IfcBaseEntity* target; 101 | cgal_placement_t inverse, own; 102 | }; 103 | typedef std::list< CGAL::Nef_polyhedron_3 > result_list_t; 104 | 105 | std::map first_product_for_geom_id; 106 | std::map reused_products; 107 | std::map placements; 108 | std::map product_geometries; 109 | 110 | // + map for reused items 111 | // + finalize() does insertion in n-ary_bool 112 | void operator()(shape_callback_item* item); 113 | 114 | // Extract the exterior component of a CGAL Polyhedron 115 | cgal_shape_t extract(const cgal_shape_t& input, extract_component component) const; 116 | void extract_in_place(cgal_shape_t& input, extract_component component) const; 117 | 118 | // Create a bounding box (six-faced Nef poly) around a CGAL Polyhedron 119 | // CGAL::Nef_polyhedron_3 create_bounding_box(const cgal_shape_t& input) const; 120 | 121 | // Completes the boolean union, extracts exterior and erodes padding radius 122 | void finalize(); 123 | 124 | bool empty() const { return empty_; } 125 | }; 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /settings.cpp: -------------------------------------------------------------------------------- 1 | #include "settings.h" 2 | 3 | #include 4 | 5 | // Parse the command line settings and do basic initialization 6 | int parse_command_line(geobim_settings& settings, int argc, char ** argv) { 7 | namespace po = boost::program_options; 8 | 9 | std::string entities, radii; 10 | 11 | size_t threads; 12 | typedef po::command_line_parser command_line_parser; 13 | po::options_description options("Command line options"); 14 | options.add_options() 15 | ("help,h", "display usage information") 16 | ("version,v", "display version information") 17 | ("debug,d", "more verbose log messages") 18 | ("spherical-padding,s", "use spherical padding volume") 19 | ("openings,o", "whether to process opening subtractions") 20 | ("timings,t", "print timings after execution") 21 | ("no-erosion,n", "don't apply erosion after union") 22 | //("exact-segmentation,e", "use exact kernel in proximity tree for semantic association (not recommended)") 23 | ("openings-posthoc,O", "whether to process opening subtractions posthoc") 24 | ("minkowski-triangles,T", "force minkowski sum on individual triangles, slow..") 25 | ("entities,e", new po::typed_value(&entities), "semicolon separated list of IFC entities to include or exclude") 26 | ("exclude,x", "entities are to be excluded") 27 | ("files", po::value>()->multitoken(), "input and output filenames") 28 | ("output-file", new po::typed_value(&settings.output_filename), "output OBJ file") 29 | ("radii", new po::typed_value(&radii), "comma separated list of radii") 30 | ("threads,j", po::value(&threads), "number of processing threads") 31 | ; 32 | 33 | po::positional_options_description positional_options; 34 | positional_options.add("files", -1); 35 | 36 | po::variables_map vmap; 37 | try { 38 | po::store(command_line_parser(argc, argv). 39 | options(options).positional(positional_options).run(), vmap); 40 | } catch (const std::exception& e) { 41 | std::cerr << e.what() << std::endl; 42 | } 43 | 44 | po::notify(vmap); 45 | 46 | if (vmap.count("version")) { 47 | return 0; 48 | } else if (vmap.count("help")) { 49 | return 0; 50 | } 51 | 52 | if (!vmap.count("files")) { 53 | return 1; 54 | } else { 55 | const auto& files = vmap["files"].as>(); 56 | if (files.size() < 2) { 57 | return 1; 58 | } 59 | settings.input_filenames.assign(files.begin(), files.end() - 1); 60 | settings.output_filename = files.back(); 61 | } 62 | 63 | // using Kernel_::FT creates weird segfaults, probably due to how ifopsh constructs the box, coordinates components cannot be shared? 64 | if (vmap.count("radii")) { 65 | boost::split(settings.radii, vmap["radii"].as(), boost::is_any_of(",")); 66 | std::sort(settings.radii.begin(), settings.radii.end(), [](const std::string& s, const std::string& t) { 67 | return boost::lexical_cast(s) < boost::lexical_cast(t); 68 | }); 69 | } 70 | 71 | if (settings.radii.empty()) { 72 | // Binary search will be used. 73 | } 74 | 75 | settings.apply_openings = vmap.count("openings"); 76 | settings.no_erosion = vmap.count("no-erosion"); 77 | settings.apply_openings_posthoc = vmap.count("openings-posthoc"); 78 | settings.exact_segmentation = vmap.count("exact-segmentation"); 79 | settings.debug = vmap.count("debug"); 80 | settings.minkowski_triangles = vmap.count("minkowski-triangles"); 81 | settings.spherical_padding = vmap.count("spherical-padding"); 82 | if (vmap.count("threads")) { 83 | settings.threads = threads; 84 | } 85 | 86 | std::transform(settings.input_filenames.begin(), settings.input_filenames.end(), std::back_inserter(settings.file), [](const std::string& s) { 87 | IfcParse::IfcFile* f = new IfcParse::IfcFile(s); 88 | if (!f->good()) { 89 | std::cerr << "[Error] Unable to parse input file '" << s << "'"; 90 | delete f; 91 | f = nullptr; 92 | } 93 | return f; 94 | }); 95 | 96 | for (auto& f : settings.file) { 97 | if (!f) { 98 | return 1; 99 | } 100 | } 101 | 102 | settings.settings.set(ifcopenshell::geometry::settings::USE_WORLD_COORDS, false); 103 | settings.settings.set(ifcopenshell::geometry::settings::WELD_VERTICES, false); 104 | settings.settings.set(ifcopenshell::geometry::settings::SEW_SHELLS, true); 105 | settings.settings.set(ifcopenshell::geometry::settings::CONVERT_BACK_UNITS, true); 106 | settings.settings.set(ifcopenshell::geometry::settings::DISABLE_TRIANGULATION, true); 107 | settings.settings.set(ifcopenshell::geometry::settings::DISABLE_OPENING_SUBTRACTIONS, !settings.apply_openings); 108 | 109 | if (vmap.count("entities")) { 110 | std::vector tokens; 111 | boost::split(tokens, vmap["entities"].as(), boost::is_any_of(";")); 112 | if (!tokens.empty()) { 113 | settings.entity_names.emplace(); 114 | settings.entity_names->clear(); 115 | settings.entity_names->insert(tokens.begin(), tokens.end()); 116 | } 117 | settings.entity_names_included = !vmap.count("exclude"); 118 | } 119 | 120 | return 0; 121 | } 122 | -------------------------------------------------------------------------------- /settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H 2 | #define SETTINGS_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | // Global settings that are derived from the command line invocation 14 | // parsed by Boost.ProgramOptions 15 | struct geobim_settings { 16 | std::vector input_filenames; 17 | std::string output_filename; 18 | std::vector radii; 19 | bool apply_openings, apply_openings_posthoc, debug, exact_segmentation, minkowski_triangles, no_erosion, spherical_padding; 20 | ifcopenshell::geometry::settings settings; 21 | boost::optional> entity_names; 22 | bool entity_names_included; 23 | std::vector file; 24 | boost::optional threads; 25 | }; 26 | 27 | // Parse the command line settings and do basic initialization 28 | int parse_command_line(geobim_settings& settings, int argc, char** argv); 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /test/fixtures/same_wall_different_material_intersecting.ifc: -------------------------------------------------------------------------------- 1 | ISO-10303-21; 2 | HEADER; 3 | FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); 4 | FILE_NAME('same_wall_different_material.ifc','2017-10-04T10:18:17',(''),(''),'IfcOpenShell-0.5.0-dev','IfcOpenShell-0.5.0-dev',''); 5 | FILE_SCHEMA(('IFC2X3')); 6 | ENDSEC; 7 | DATA; 8 | #1=IFCPERSON($,$,'',$,$,$,$,$); 9 | #2=IFCORGANIZATION($,'',$,$,$); 10 | #3=IFCPERSONANDORGANIZATION(#1,#2,$); 11 | #4=IFCAPPLICATION(#2,'0.5.0-dev','IfcOpenShell-0.5.0-dev',''); 12 | #5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1507112297.06); 13 | #6=IFCDIRECTION((1.,0.,0.)); 14 | #7=IFCDIRECTION((0.,0.,1.)); 15 | #8=IFCCARTESIANPOINT((0.,0.,0.)); 16 | #9=IFCAXIS2PLACEMENT3D(#8,#7,#6); 17 | #10=IFCDIRECTION((0.,1.,0.)); 18 | #11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); 19 | #12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 20 | #13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); 21 | #14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); 22 | #15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); 23 | #16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); 24 | #17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); 25 | #18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); 26 | #19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); 27 | #20=IFCPROJECT('0cyJ4eJAPFPe_n3Hv_GVIY',#5,'',$,$,$,$,(#11),#19); 28 | #21=IFCCARTESIANPOINT((0.,0.,0.)); 29 | #22=IFCDIRECTION((0.,0.,1.)); 30 | #23=IFCDIRECTION((1.,0.,0.)); 31 | #24=IFCAXIS2PLACEMENT3D(#21,#22,#23); 32 | #25=IFCLOCALPLACEMENT($,#24); 33 | #26=IFCSITE('2joly$7s56vuNdMwWPrcyW',#5,'Site',$,$,#25,$,$,.ELEMENT.,$,$,$,$,$); 34 | #27=IFCCARTESIANPOINT((0.,0.,0.)); 35 | #28=IFCDIRECTION((0.,0.,1.)); 36 | #29=IFCDIRECTION((1.,0.,0.)); 37 | #30=IFCAXIS2PLACEMENT3D(#27,#28,#29); 38 | #31=IFCLOCALPLACEMENT(#25,#30); 39 | #32=IFCBUILDING('2ifJ5W4yf03uwJFn71vz3M',#5,'Building',$,$,#31,$,$,.ELEMENT.,$,$,$); 40 | #33=IFCCARTESIANPOINT((0.,0.,0.)); 41 | #34=IFCDIRECTION((0.,0.,1.)); 42 | #35=IFCDIRECTION((1.,0.,0.)); 43 | #36=IFCAXIS2PLACEMENT3D(#33,#34,#35); 44 | #37=IFCLOCALPLACEMENT(#31,#36); 45 | #38=IFCBUILDINGSTOREY('1v0G48V0L8SPtOnTYuDuc7',#5,'Storey',$,$,#37,$,$,.ELEMENT.,0.); 46 | #39=IFCRELAGGREGATES('0q3NZw4jj0EvkKpU8GwYFQ',#5,'Building Container',$,#32,(#38)); 47 | #40=IFCRELAGGREGATES('18XjkqO3TBCuV71jGUr1uL',#5,'Site Container',$,#26,(#32)); 48 | #41=IFCRELAGGREGATES('2RUmN7CUn9_8fUqUbjBwax',#5,'Project Container',$,#20,(#26)); 49 | #42=IFCCARTESIANPOINT((0.,0.,0.)); 50 | #43=IFCDIRECTION((0.,0.,1.)); 51 | #44=IFCDIRECTION((1.,0.,0.)); 52 | #45=IFCAXIS2PLACEMENT3D(#42,#43,#44); 53 | #46=IFCLOCALPLACEMENT(#37,#45); 54 | #47=IFCCARTESIANPOINT((1.,0.,0.)); 55 | #48=IFCDIRECTION((0.,0.,1.)); 56 | #49=IFCDIRECTION((1.,0.,0.)); 57 | #50=IFCAXIS2PLACEMENT3D(#47,#48,#49); 58 | #51=IFCLOCALPLACEMENT(#37,#50); 59 | #52=IFCCARTESIANPOINT((0.,0.,0.)); 60 | #53=IFCDIRECTION((0.,0.,1.)); 61 | #54=IFCDIRECTION((1.,0.,0.)); 62 | #55=IFCAXIS2PLACEMENT3D(#52,#53,#54); 63 | #56=IFCCARTESIANPOINT((0.,0.)); 64 | #57=IFCDIRECTION((1.,0.)); 65 | #58=IFCAXIS2PLACEMENT2D(#56,#57); 66 | #59=IFCRECTANGLEPROFILEDEF(.AREA.,$,#58,2.,2.); 67 | #60=IFCDIRECTION((0.,0.,1.)); 68 | #61=IFCEXTRUDEDAREASOLID(#59,#55,#60,3.); 69 | #62=IFCSHAPEREPRESENTATION(#11,'Body','SweptSolid',(#61)); 70 | #63=IFCPRODUCTDEFINITIONSHAPE($,$,(#62)); 71 | #64=IFCWALLSTANDARDCASE('0PRMidDVX1$Qhu1ANYjgKr',#5,'Wall1',$,$,#46,#63,$); 72 | #65=IFCWALLSTANDARDCASE('3bdBQ15d16_RwC18lCY2eB',#5,'Wall2',$,$,#51,#63,$); 73 | #66=IFCMATERIAL('red material'); 74 | #67=IFCMATERIALLAYER(#66,2.,$); 75 | #68=IFCMATERIALLAYERSET((#67),$); 76 | #69=IFCMATERIALLAYERSETUSAGE(#68,.AXIS2.,.POSITIVE.,0.); 77 | #70=IFCCOLOURRGB('Red',1.,0.,0.); 78 | #71=IFCSURFACESTYLESHADING(#70); 79 | #72=IFCSURFACESTYLE($,.BOTH.,(#71)); 80 | #73=IFCPRESENTATIONSTYLEASSIGNMENT((#72)); 81 | #74=IFCSTYLEDITEM($,(#73),$); 82 | #75=IFCSTYLEDREPRESENTATION(#11,$,$,(#74)); 83 | #76=IFCMATERIALDEFINITIONREPRESENTATION($,$,(#75),#66); 84 | #77=IFCMATERIAL('blue material'); 85 | #78=IFCMATERIALLAYER(#77,2.,$); 86 | #79=IFCMATERIALLAYERSET((#78),$); 87 | #80=IFCMATERIALLAYERSETUSAGE(#79,.AXIS2.,.POSITIVE.,0.); 88 | #81=IFCCOLOURRGB('Blue',0.,0.,1.); 89 | #82=IFCSURFACESTYLESHADING(#81); 90 | #83=IFCSURFACESTYLE($,.BOTH.,(#82)); 91 | #84=IFCPRESENTATIONSTYLEASSIGNMENT((#83)); 92 | #85=IFCSTYLEDITEM($,(#84),$); 93 | #86=IFCSTYLEDREPRESENTATION(#11,$,$,(#85)); 94 | #87=IFCMATERIALDEFINITIONREPRESENTATION($,$,(#86),#77); 95 | #88=IFCRELASSOCIATESMATERIAL('2wD9fcmQnC6PyZl2XJGSpJ',#5,$,$,(#64),#69); 96 | #89=IFCRELASSOCIATESMATERIAL('1XR4iTfZHCduqo5B6$U1t3',#5,$,$,(#65),#80); 97 | ENDSEC; 98 | END-ISO-10303-21; 99 | -------------------------------------------------------------------------------- /test/fixtures/same_wall_different_material_touching.ifc: -------------------------------------------------------------------------------- 1 | ISO-10303-21; 2 | HEADER; 3 | FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); 4 | FILE_NAME('same_wall_different_material.ifc','2017-10-04T10:18:17',(''),(''),'IfcOpenShell-0.5.0-dev','IfcOpenShell-0.5.0-dev',''); 5 | FILE_SCHEMA(('IFC2X3')); 6 | ENDSEC; 7 | DATA; 8 | #1=IFCPERSON($,$,'',$,$,$,$,$); 9 | #2=IFCORGANIZATION($,'',$,$,$); 10 | #3=IFCPERSONANDORGANIZATION(#1,#2,$); 11 | #4=IFCAPPLICATION(#2,'0.5.0-dev','IfcOpenShell-0.5.0-dev',''); 12 | #5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1507112297.06); 13 | #6=IFCDIRECTION((1.,0.,0.)); 14 | #7=IFCDIRECTION((0.,0.,1.)); 15 | #8=IFCCARTESIANPOINT((0.,0.,0.)); 16 | #9=IFCAXIS2PLACEMENT3D(#8,#7,#6); 17 | #10=IFCDIRECTION((0.,1.,0.)); 18 | #11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); 19 | #12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 20 | #13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); 21 | #14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); 22 | #15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); 23 | #16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); 24 | #17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); 25 | #18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); 26 | #19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); 27 | #20=IFCPROJECT('0cyJ4eJAPFPe_n3Hv_GVIY',#5,'',$,$,$,$,(#11),#19); 28 | #21=IFCCARTESIANPOINT((0.,0.,0.)); 29 | #22=IFCDIRECTION((0.,0.,1.)); 30 | #23=IFCDIRECTION((1.,0.,0.)); 31 | #24=IFCAXIS2PLACEMENT3D(#21,#22,#23); 32 | #25=IFCLOCALPLACEMENT($,#24); 33 | #26=IFCSITE('2joly$7s56vuNdMwWPrcyW',#5,'Site',$,$,#25,$,$,.ELEMENT.,$,$,$,$,$); 34 | #27=IFCCARTESIANPOINT((0.,0.,0.)); 35 | #28=IFCDIRECTION((0.,0.,1.)); 36 | #29=IFCDIRECTION((1.,0.,0.)); 37 | #30=IFCAXIS2PLACEMENT3D(#27,#28,#29); 38 | #31=IFCLOCALPLACEMENT(#25,#30); 39 | #32=IFCBUILDING('2ifJ5W4yf03uwJFn71vz3M',#5,'Building',$,$,#31,$,$,.ELEMENT.,$,$,$); 40 | #33=IFCCARTESIANPOINT((0.,0.,0.)); 41 | #34=IFCDIRECTION((0.,0.,1.)); 42 | #35=IFCDIRECTION((1.,0.,0.)); 43 | #36=IFCAXIS2PLACEMENT3D(#33,#34,#35); 44 | #37=IFCLOCALPLACEMENT(#31,#36); 45 | #38=IFCBUILDINGSTOREY('1v0G48V0L8SPtOnTYuDuc7',#5,'Storey',$,$,#37,$,$,.ELEMENT.,0.); 46 | #39=IFCRELAGGREGATES('0q3NZw4jj0EvkKpU8GwYFQ',#5,'Building Container',$,#32,(#38)); 47 | #40=IFCRELAGGREGATES('18XjkqO3TBCuV71jGUr1uL',#5,'Site Container',$,#26,(#32)); 48 | #41=IFCRELAGGREGATES('2RUmN7CUn9_8fUqUbjBwax',#5,'Project Container',$,#20,(#26)); 49 | #42=IFCCARTESIANPOINT((0.,0.,0.)); 50 | #43=IFCDIRECTION((0.,0.,1.)); 51 | #44=IFCDIRECTION((1.,0.,0.)); 52 | #45=IFCAXIS2PLACEMENT3D(#42,#43,#44); 53 | #46=IFCLOCALPLACEMENT(#37,#45); 54 | #47=IFCCARTESIANPOINT((2.,0.,0.)); 55 | #48=IFCDIRECTION((0.,0.,1.)); 56 | #49=IFCDIRECTION((1.,0.,0.)); 57 | #50=IFCAXIS2PLACEMENT3D(#47,#48,#49); 58 | #51=IFCLOCALPLACEMENT(#37,#50); 59 | #52=IFCCARTESIANPOINT((0.,0.,0.)); 60 | #53=IFCDIRECTION((0.,0.,1.)); 61 | #54=IFCDIRECTION((1.,0.,0.)); 62 | #55=IFCAXIS2PLACEMENT3D(#52,#53,#54); 63 | #56=IFCCARTESIANPOINT((0.,0.)); 64 | #57=IFCDIRECTION((1.,0.)); 65 | #58=IFCAXIS2PLACEMENT2D(#56,#57); 66 | #59=IFCRECTANGLEPROFILEDEF(.AREA.,$,#58,2.,2.); 67 | #60=IFCDIRECTION((0.,0.,1.)); 68 | #61=IFCEXTRUDEDAREASOLID(#59,#55,#60,3.); 69 | #62=IFCSHAPEREPRESENTATION(#11,'Body','SweptSolid',(#61)); 70 | #63=IFCPRODUCTDEFINITIONSHAPE($,$,(#62)); 71 | #64=IFCWALLSTANDARDCASE('0PRMidDVX1$Qhu1ANYjgKr',#5,'Wall1',$,$,#46,#63,$); 72 | #65=IFCWALLSTANDARDCASE('3bdBQ15d16_RwC18lCY2eB',#5,'Wall2',$,$,#51,#63,$); 73 | #66=IFCMATERIAL('red material'); 74 | #67=IFCMATERIALLAYER(#66,2.,$); 75 | #68=IFCMATERIALLAYERSET((#67),$); 76 | #69=IFCMATERIALLAYERSETUSAGE(#68,.AXIS2.,.POSITIVE.,0.); 77 | #70=IFCCOLOURRGB('Red',1.,0.,0.); 78 | #71=IFCSURFACESTYLESHADING(#70); 79 | #72=IFCSURFACESTYLE($,.BOTH.,(#71)); 80 | #73=IFCPRESENTATIONSTYLEASSIGNMENT((#72)); 81 | #74=IFCSTYLEDITEM($,(#73),$); 82 | #75=IFCSTYLEDREPRESENTATION(#11,$,$,(#74)); 83 | #76=IFCMATERIALDEFINITIONREPRESENTATION($,$,(#75),#66); 84 | #77=IFCMATERIAL('blue material'); 85 | #78=IFCMATERIALLAYER(#77,2.,$); 86 | #79=IFCMATERIALLAYERSET((#78),$); 87 | #80=IFCMATERIALLAYERSETUSAGE(#79,.AXIS2.,.POSITIVE.,0.); 88 | #81=IFCCOLOURRGB('Blue',0.,0.,1.); 89 | #82=IFCSURFACESTYLESHADING(#81); 90 | #83=IFCSURFACESTYLE($,.BOTH.,(#82)); 91 | #84=IFCPRESENTATIONSTYLEASSIGNMENT((#83)); 92 | #85=IFCSTYLEDITEM($,(#84),$); 93 | #86=IFCSTYLEDREPRESENTATION(#11,$,$,(#85)); 94 | #87=IFCMATERIALDEFINITIONREPRESENTATION($,$,(#86),#77); 95 | #88=IFCRELASSOCIATESMATERIAL('2wD9fcmQnC6PyZl2XJGSpJ',#5,$,$,(#64),#69); 96 | #89=IFCRELASSOCIATESMATERIAL('1XR4iTfZHCduqo5B6$U1t3',#5,$,$,(#65),#80); 97 | ENDSEC; 98 | END-ISO-10303-21; 99 | -------------------------------------------------------------------------------- /test/fixtures/schependomlaan_covering.ifc: -------------------------------------------------------------------------------- 1 | ISO-10303-21; 2 | HEADER; 3 | FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); 4 | FILE_NAME('','2020-07-18T11:50:34',(),(),'IfcOpenShell 0.6.0b0','IfcOpenShell 0.6.0b0',''); 5 | FILE_SCHEMA(('IFC2X3')); 6 | ENDSEC; 7 | DATA; 8 | #1=IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Amsterdamsestraatweg 43'),$,'Amersfoort',$,'3812 RP',$); 9 | #2=IFCPERSON($,$,'architect',$,$,$,$,(#1)); 10 | #3=IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Amsterdamsestraatweg 43'),$,'Amersfoort',$,'3812 RP',$); 11 | #4=IFCORGANIZATION($,'ROOT bv',$,$,(#3)); 12 | #5=IFCPERSONANDORGANIZATION(#2,#4,$); 13 | #6=IFCORGANIZATION('GS','Graphisoft','Graphisoft',$,$); 14 | #7=IFCAPPLICATION(#6,'18.0.0','ArchiCAD-64','IFC2x3 add-on version: 6000 NED FULL'); 15 | #8=IFCOWNERHISTORY(#5,#7,$,.ADDED.,$,$,$,1440679749); 16 | #9=IFCCARTESIANPOINT((0.,0.,0.)); 17 | #10=IFCDIRECTION((0.,0.,1.)); 18 | #11=IFCDIRECTION((1.,0.,0.)); 19 | #12=IFCAXIS2PLACEMENT3D(#9,#10,#11); 20 | #13=IFCDIRECTION((0.,1.)); 21 | #14=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#12,#13); 22 | #15=IFCCARTESIANPOINT((0.,0.,0.)); 23 | #16=IFCDIRECTION((0.,0.,1.)); 24 | #17=IFCDIRECTION((1.,0.,0.)); 25 | #18=IFCAXIS2PLACEMENT3D(#15,#16,#17); 26 | #19=IFCDIRECTION((0.,1.)); 27 | #20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.E-05,#18,#19); 28 | #21=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.); 29 | #22=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); 30 | #23=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); 31 | #24=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 32 | #25=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); 33 | #26=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.0174532925199),#25); 34 | #27=IFCCONVERSIONBASEDUNIT(#24,.PLANEANGLEUNIT.,'DEGREE',#26); 35 | #28=IFCSIUNIT(*,.SOLIDANGLEUNIT.,$,.STERADIAN.); 36 | #29=IFCMONETARYUNIT(.EUR.); 37 | #30=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 38 | #31=IFCSIUNIT(*,.TIMEUNIT.,$,.SECOND.); 39 | #32=IFCMEASUREWITHUNIT(IFCTIMEMEASURE(31556926.),#31); 40 | #33=IFCCONVERSIONBASEDUNIT(#30,.TIMEUNIT.,'Year',#32); 41 | #34=IFCSIUNIT(*,.MASSUNIT.,$,.GRAM.); 42 | #35=IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.DEGREE_CELSIUS.); 43 | #36=IFCSIUNIT(*,.LUMINOUSINTENSITYUNIT.,$,.LUMEN.); 44 | #37=IFCUNITASSIGNMENT((#21,#22,#23,#27,#28,#29,#33,#34,#35,#36)); 45 | #38=IFCPROJECT('344O7vICcwH8qAEnwJDjSU',#8,'10 Appartementen Schependomlaan',$,$,$,$,(#14,#20),#37); 46 | #39=IFCCARTESIANPOINT((0.,0.,0.)); 47 | #40=IFCDIRECTION((0.,0.,1.)); 48 | #41=IFCDIRECTION((1.,0.,0.)); 49 | #42=IFCAXIS2PLACEMENT3D(#39,#40,#41); 50 | #43=IFCLOCALPLACEMENT($,#42); 51 | #44=IFCCARTESIANPOINT((0.,0.,0.)); 52 | #45=IFCDIRECTION((0.,0.,1.)); 53 | #46=IFCDIRECTION((1.,0.,0.)); 54 | #47=IFCAXIS2PLACEMENT3D(#44,#45,#46); 55 | #48=IFCLOCALPLACEMENT(#43,#47); 56 | #49=IFCCARTESIANPOINT((0.,0.,12000.)); 57 | #50=IFCDIRECTION((0.,0.,1.)); 58 | #51=IFCDIRECTION((1.,0.,0.)); 59 | #52=IFCAXIS2PLACEMENT3D(#49,#50,#51); 60 | #53=IFCLOCALPLACEMENT(#48,#52); 61 | #54=IFCCARTESIANPOINT((10325.996433,11632.,800.)); 62 | #55=IFCDIRECTION((0.,0.,1.)); 63 | #56=IFCDIRECTION((2.03707240831E-06,0.999999999998,0.)); 64 | #57=IFCAXIS2PLACEMENT3D(#54,#55,#56); 65 | #58=IFCLOCALPLACEMENT(#53,#57); 66 | #59=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#14,$,.MODEL_VIEW.,$); 67 | #60=IFCCARTESIANPOINT((13.4468105814,-14.7790265886,183.50195351)); 68 | #61=IFCCARTESIANPOINT((10.6549940332,-13.6735634462,184.250016828)); 69 | #62=IFCCARTESIANPOINT((10.6549878841,-10.6549714743,184.250016828)); 70 | #63=IFCCARTESIANPOINT((20.6550155824,-20.6549714743,181.57052489)); 71 | #64=IFCCARTESIANPOINT((14.7781232157,-28.3483455299,183.145237098)); 72 | #65=IFCCARTESIANPOINT((14.7781176731,-25.292726297,183.145237098)); 73 | #66=IFCPOLYLOOP((#60,#61,#62,#63,#64,#65)); 74 | #67=IFCFACEOUTERBOUND(#66,.T.); 75 | #68=IFCFACE((#67)); 76 | #69=IFCCARTESIANPOINT((2438.49220839,-6.06660719548,189.397619416)); 77 | #70=IFCCARTESIANPOINT((5.5073841706,-6.06660719548,189.397619416)); 78 | #71=IFCCARTESIANPOINT((2430.5528218,-14.7790265886,183.50195351)); 79 | #72=IFCCARTESIANPOINT((2433.34462666,-13.6735637121,184.250016648)); 80 | #73=IFCPOLYLOOP((#69,#70,#61,#60,#71,#72)); 81 | #74=IFCFACEOUTERBOUND(#73,.T.); 82 | #75=IFCFACE((#74)); 83 | #76=IFCCARTESIANPOINT((3.33447065683,-5.10954878702,191.570533319)); 84 | #77=IFCCARTESIANPOINT((3.33446704085,-3.33446339864,191.570533319)); 85 | #78=IFCPOLYLOOP((#76,#77,#62,#61,#70)); 86 | #79=IFCFACEOUTERBOUND(#78,.T.); 87 | #80=IFCFACE((#79)); 88 | #81=IFCCARTESIANPOINT((2433.34462051,-10.6549714743,184.250016648)); 89 | #82=IFCCARTESIANPOINT((2423.34464374,-20.6549714743,181.570527572)); 90 | #83=IFCPOLYLOOP((#62,#81,#82,#63)); 91 | #84=IFCFACEOUTERBOUND(#83,.T.); 92 | #85=IFCFACE((#84)); 93 | #86=IFCCARTESIANPOINT((2429.22155744,-28.3483455299,183.145237098)); 94 | #87=IFCCARTESIANPOINT((2418.7068968,-28.3483455299,183.145237098)); 95 | #88=IFCCARTESIANPOINT((2418.64895132,-28.5131176352,183.178963299)); 96 | #89=IFCCARTESIANPOINT((25.3507300069,-28.5131176352,183.178963299)); 97 | #90=IFCCARTESIANPOINT((25.2927838586,-28.3483455299,183.145237098)); 98 | #91=IFCPOLYLOOP((#64,#63,#82,#86,#87,#88,#89,#90)); 99 | #92=IFCFACEOUTERBOUND(#91,.T.); 100 | #93=IFCFACE((#92)); 101 | #94=IFCCARTESIANPOINT((25.2927776341,-25.2927259958,183.145237098)); 102 | #95=IFCPOLYLOOP((#90,#94,#65,#64)); 103 | #96=IFCFACEOUTERBOUND(#95,.T.); 104 | #97=IFCFACE((#96)); 105 | #98=IFCCARTESIANPOINT((2418.70689058,-25.2927259958,183.145237098)); 106 | #99=IFCCARTESIANPOINT((2429.22155754,-25.292726297,183.145237098)); 107 | #100=IFCPOLYLOOP((#71,#60,#65,#94,#98,#99)); 108 | #101=IFCFACEOUTERBOUND(#100,.T.); 109 | #102=IFCFACE((#101)); 110 | #103=IFCCARTESIANPOINT((1.33707130573,-1.82627423595,199.024908991)); 111 | #104=IFCCARTESIANPOINT((2440.66511001,-5.10955230993,191.57052532)); 112 | #105=IFCCARTESIANPOINT((2442.66249813,-1.82627423595,199.024908991)); 113 | #106=IFCPOLYLOOP((#103,#76,#70,#69,#104,#105)); 114 | #107=IFCFACEOUTERBOUND(#106,.T.); 115 | #108=IFCFACE((#107)); 116 | #109=IFCPOLYLOOP((#72,#71,#99,#86,#82,#81)); 117 | #110=IFCFACEOUTERBOUND(#109,.T.); 118 | #111=IFCFACE((#110)); 119 | #112=IFCCARTESIANPOINT((2440.66510639,-3.33446339862,191.57052532)); 120 | #113=IFCPOLYLOOP((#81,#112,#104,#69,#72)); 121 | #114=IFCFACEOUTERBOUND(#113,.T.); 122 | #115=IFCFACE((#114)); 123 | #116=IFCCARTESIANPOINT((0.654977289166,-2.20089761218,201.570522994)); 124 | #117=IFCCARTESIANPOINT((0.654974140003,-0.654971474315,201.570522994)); 125 | #118=IFCPOLYLOOP((#76,#103,#116,#117,#77)); 126 | #119=IFCFACEOUTERBOUND(#118,.T.); 127 | #120=IFCFACE((#119)); 128 | #121=IFCPOLYLOOP((#77,#112,#81,#62)); 129 | #122=IFCFACEOUTERBOUND(#121,.T.); 130 | #123=IFCFACE((#122)); 131 | #124=IFCPOLYLOOP((#98,#87,#86,#99)); 132 | #125=IFCFACEOUTERBOUND(#124,.T.); 133 | #126=IFCFACE((#125)); 134 | #127=IFCCARTESIANPOINT((2409.61491956,-34.3847137228,188.436824508)); 135 | #128=IFCCARTESIANPOINT((2410.80261825,-35.1073311798,187.745575548)); 136 | #129=IFCPOLYLOOP((#87,#98,#127,#128,#88)); 137 | #130=IFCFACEOUTERBOUND(#129,.T.); 138 | #131=IFCFACE((#130)); 139 | #132=IFCCARTESIANPOINT((33.1970899494,-35.1073311798,187.745575548)); 140 | #133=IFCPOLYLOOP((#132,#89,#88,#128)); 141 | #134=IFCFACEOUTERBOUND(#133,.T.); 142 | #135=IFCFACE((#134)); 143 | #136=IFCCARTESIANPOINT((34.384785696,-34.3847137228,188.436824508)); 144 | #137=IFCPOLYLOOP((#94,#90,#89,#132,#136)); 145 | #138=IFCFACEOUTERBOUND(#137,.T.); 146 | #139=IFCFACE((#138)); 147 | #140=IFCPOLYLOOP((#94,#136,#127,#98)); 148 | #141=IFCFACEOUTERBOUND(#140,.T.); 149 | #142=IFCFACE((#141)); 150 | #143=IFCCARTESIANPOINT((2443.34459371,-0.654971474313,201.570523069)); 151 | #144=IFCCARTESIANPOINT((2443.34459686,-2.2008976232,201.570523069)); 152 | #145=IFCPOLYLOOP((#105,#104,#112,#143,#144)); 153 | #146=IFCFACEOUTERBOUND(#145,.T.); 154 | #147=IFCFACE((#146)); 155 | #148=IFCCARTESIANPOINT((2441.26910108,-3.35790856981,209.432561196)); 156 | #149=IFCCARTESIANPOINT((2.73047854093,-3.35790856982,209.432561196)); 157 | #150=IFCPOLYLOOP((#116,#103,#105,#144,#148,#149)); 158 | #151=IFCFACEOUTERBOUND(#150,.T.); 159 | #152=IFCFACE((#151)); 160 | #153=IFCCARTESIANPOINT((3.26099923123,-5.0751684328,211.442173033)); 161 | #154=IFCCARTESIANPOINT((3.26099553562,-3.26099044494,211.442173033)); 162 | #155=IFCPOLYLOOP((#117,#116,#149,#153,#154)); 163 | #156=IFCFACEOUTERBOUND(#155,.T.); 164 | #157=IFCFACE((#156)); 165 | #158=IFCPOLYLOOP((#77,#117,#143,#112)); 166 | #159=IFCFACEOUTERBOUND(#158,.T.); 167 | #160=IFCFACE((#159)); 168 | #161=IFCPOLYLOOP((#136,#132,#128,#127)); 169 | #162=IFCFACEOUTERBOUND(#161,.T.); 170 | #163=IFCFACE((#162)); 171 | #164=IFCCARTESIANPOINT((2440.73858398,-3.26099044493,211.442171941)); 172 | #165=IFCCARTESIANPOINT((2440.73858768,-5.07516750009,211.442171941)); 173 | #166=IFCPOLYLOOP((#144,#143,#164,#165,#148)); 174 | #167=IFCFACEOUTERBOUND(#166,.T.); 175 | #168=IFCFACE((#167)); 176 | #169=IFCCARTESIANPOINT((2434.88206055,-10.1919852391,217.43009358)); 177 | #170=IFCCARTESIANPOINT((9.1175449144,-10.1919852391,217.43009358)); 178 | #171=IFCPOLYLOOP((#153,#149,#148,#165,#169,#170)); 179 | #172=IFCFACEOUTERBOUND(#171,.T.); 180 | #173=IFCFACE((#172)); 181 | #174=IFCCARTESIANPOINT((10.3999439692,-14.3906690009,218.741254262)); 182 | #175=IFCCARTESIANPOINT((10.3999358398,-10.3999138692,218.741254262)); 183 | #176=IFCPOLYLOOP((#154,#153,#170,#174,#175)); 184 | #177=IFCFACEOUTERBOUND(#176,.T.); 185 | #178=IFCFACE((#177)); 186 | #179=IFCPOLYLOOP((#117,#154,#164,#143)); 187 | #180=IFCFACEOUTERBOUND(#179,.T.); 188 | #181=IFCFACE((#180)); 189 | #182=IFCCARTESIANPOINT((2433.59967812,-14.390670569,218.741254752)); 190 | #183=IFCCARTESIANPOINT((2433.59966999,-10.3999138693,218.741254752)); 191 | #184=IFCPOLYLOOP((#182,#169,#165,#164,#183)); 192 | #185=IFCFACEOUTERBOUND(#184,.T.); 193 | #186=IFCFACE((#185)); 194 | #187=IFCCARTESIANPOINT((2427.26129553,-20.2335070956,220.565849783)); 195 | #188=IFCCARTESIANPOINT((2423.76610054,-20.2335058437,220.565847002)); 196 | #189=IFCCARTESIANPOINT((20.2335470608,-20.2335058437,220.565847002)); 197 | #190=IFCCARTESIANPOINT((16.7383520678,-20.2335070956,220.565849783)); 198 | #191=IFCPOLYLOOP((#170,#169,#182,#187,#188,#189,#190,#174)); 199 | #192=IFCFACEOUTERBOUND(#191,.T.); 200 | #193=IFCFACE((#192)); 201 | #194=IFCCARTESIANPOINT((16.7385657734,-120.701940888,220.565847002)); 202 | #195=IFCCARTESIANPOINT((20.2113747138,-20.2113247599,221.565607678)); 203 | #196=IFCPOLYLOOP((#175,#174,#190,#194,#195)); 204 | #197=IFCFACEOUTERBOUND(#196,.T.); 205 | #198=IFCFACE((#197)); 206 | #199=IFCPOLYLOOP((#164,#154,#175,#183)); 207 | #200=IFCFACEOUTERBOUND(#199,.T.); 208 | #201=IFCFACE((#200)); 209 | #202=IFCCARTESIANPOINT((2423.78828157,-20.2113247599,221.565605152)); 210 | #203=IFCCARTESIANPOINT((2427.26149115,-120.701940888,220.565847002)); 211 | #204=IFCPOLYLOOP((#182,#183,#202,#203,#187)); 212 | #205=IFCFACEOUTERBOUND(#204,.T.); 213 | #206=IFCFACE((#205)); 214 | #207=IFCCARTESIANPOINT((2423.7663052,-120.701940888,220.565847002)); 215 | #208=IFCPOLYLOOP((#207,#188,#187,#203)); 216 | #209=IFCFACEOUTERBOUND(#208,.T.); 217 | #210=IFCFACE((#209)); 218 | #211=IFCCARTESIANPOINT((2144.55584575,-299.444332583,217.787543037)); 219 | #212=IFCCARTESIANPOINT((299.444939391,-299.444332583,217.787543037)); 220 | #213=IFCPOLYLOOP((#189,#188,#211,#212)); 221 | #214=IFCFACEOUTERBOUND(#213,.T.); 222 | #215=IFCFACE((#214)); 223 | #216=IFCCARTESIANPOINT((20.2337517223,-120.701940888,220.565847002)); 224 | #217=IFCPOLYLOOP((#194,#190,#189,#216)); 225 | #218=IFCFACEOUTERBOUND(#217,.T.); 226 | #219=IFCFACE((#218)); 227 | #220=IFCCARTESIANPOINT((199.367776686,-299.868470404,218.783368504)); 228 | #221=IFCCARTESIANPOINT((2244.63301019,-299.868470404,218.783368504)); 229 | #222=IFCPOLYLOOP((#195,#194,#216,#220,#221,#207,#203,#202)); 230 | #223=IFCFACEOUTERBOUND(#222,.T.); 231 | #224=IFCFACE((#223)); 232 | #225=IFCPOLYLOOP((#183,#175,#195,#202)); 233 | #226=IFCFACEOUTERBOUND(#225,.T.); 234 | #227=IFCFACE((#226)); 235 | #228=IFCCARTESIANPOINT((2144.55584861,-300.844684662,217.787542008)); 236 | #229=IFCPOLYLOOP((#211,#188,#207,#221,#228)); 237 | #230=IFCFACEOUTERBOUND(#229,.T.); 238 | #231=IFCFACE((#230)); 239 | #232=IFCCARTESIANPOINT((325.936001704,-325.935337749,190.7641364)); 240 | #233=IFCCARTESIANPOINT((2118.06489137,-325.935337749,190.7641364)); 241 | #234=IFCPOLYLOOP((#232,#212,#211,#233)); 242 | #235=IFCFACEOUTERBOUND(#234,.T.); 243 | #236=IFCFACE((#235)); 244 | #237=IFCCARTESIANPOINT((299.444942243,-300.844684662,217.787542008)); 245 | #238=IFCPOLYLOOP((#216,#189,#212,#237,#220)); 246 | #239=IFCFACEOUTERBOUND(#238,.T.); 247 | #240=IFCFACE((#239)); 248 | #241=IFCCARTESIANPOINT((325.936004556,-327.335689984,190.764136142)); 249 | #242=IFCCARTESIANPOINT((322.210439341,-327.335688402,190.764134943)); 250 | #243=IFCCARTESIANPOINT((329.262689729,-329.262018998,188.799091053)); 251 | #244=IFCCARTESIANPOINT((2114.7382169,-329.262018998,188.799091053)); 252 | #245=IFCCARTESIANPOINT((2121.79045944,-327.335688402,190.764134943)); 253 | #246=IFCCARTESIANPOINT((2118.06489422,-327.335689984,190.764136142)); 254 | #247=IFCPOLYLOOP((#220,#237,#241,#242,#243,#244,#245,#246,#228,#221)); 255 | #248=IFCFACEOUTERBOUND(#247,.T.); 256 | #249=IFCFACE((#248)); 257 | #250=IFCPOLYLOOP((#211,#228,#246,#233)); 258 | #251=IFCFACEOUTERBOUND(#250,.T.); 259 | #252=IFCFACE((#251)); 260 | #253=IFCPOLYLOOP((#237,#212,#232,#241)); 261 | #254=IFCFACEOUTERBOUND(#253,.T.); 262 | #255=IFCFACE((#254)); 263 | #256=IFCCARTESIANPOINT((310.26500163,-310.26436903,195.13070625)); 264 | #257=IFCCARTESIANPOINT((309.996592127,-313.721524242,194.167400252)); 265 | #258=IFCCARTESIANPOINT((322.210436965,-325.935337332,190.764134943)); 266 | #259=IFCCARTESIANPOINT((2121.79045611,-325.935337332,190.764134943)); 267 | #260=IFCCARTESIANPOINT((2134.00425118,-313.721524242,194.167400252)); 268 | #261=IFCCARTESIANPOINT((2133.7358276,-310.26436903,195.13070625)); 269 | #262=IFCPOLYLOOP((#256,#257,#258,#232,#233,#259,#260,#261)); 270 | #263=IFCFACEOUTERBOUND(#262,.T.); 271 | #264=IFCFACE((#263)); 272 | #265=IFCPOLYLOOP((#258,#242,#241,#232)); 273 | #266=IFCFACEOUTERBOUND(#265,.T.); 274 | #267=IFCFACE((#266)); 275 | #268=IFCCARTESIANPOINT((309.996584538,-309.995954709,194.167400503)); 276 | #269=IFCPOLYLOOP((#257,#268,#243,#242,#258)); 277 | #270=IFCFACEOUTERBOUND(#269,.T.); 278 | #271=IFCFACE((#270)); 279 | #272=IFCCARTESIANPOINT((2134.0042436,-309.995954709,194.167400503)); 280 | #273=IFCPOLYLOOP((#268,#272,#244,#243)); 281 | #274=IFCFACEOUTERBOUND(#273,.T.); 282 | #275=IFCFACE((#274)); 283 | #276=IFCPOLYLOOP((#272,#260,#259,#245,#244)); 284 | #277=IFCFACEOUTERBOUND(#276,.T.); 285 | #278=IFCFACE((#277)); 286 | #279=IFCPOLYLOOP((#259,#233,#246,#245)); 287 | #280=IFCFACEOUTERBOUND(#279,.T.); 288 | #281=IFCFACE((#280)); 289 | #282=IFCPOLYLOOP((#256,#268,#257)); 290 | #283=IFCFACEOUTERBOUND(#282,.T.); 291 | #284=IFCFACE((#283)); 292 | #285=IFCPOLYLOOP((#272,#261,#260)); 293 | #286=IFCFACEOUTERBOUND(#285,.T.); 294 | #287=IFCFACE((#286)); 295 | #288=IFCPOLYLOOP((#272,#268,#256,#261)); 296 | #289=IFCFACEOUTERBOUND(#288,.T.); 297 | #290=IFCFACE((#289)); 298 | #291=IFCCLOSEDSHELL((#68,#75,#80,#85,#93,#97,#102,#108,#111,#115,#120,#123,#126,#131,#135,#139,#142,#147,#152,#157,#160,#163,#168,#173,#178,#181,#186,#193,#198,#201,#206,#210,#215,#219,#224,#227,#231,#236,#240,#249,#252,#255,#264,#267,#271,#275,#278,#281,#284,#287,#290)); 299 | #292=IFCFACETEDBREP(#291); 300 | #293=IFCSHAPEREPRESENTATION(#59,'Body','Brep',(#292)); 301 | #294=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Plan',*,*,*,*,#20,$,.PLAN_VIEW.,$); 302 | #295=IFCCARTESIANPOINT((0.654974140003,-329.262018998,181.57052489)); 303 | #296=IFCBOUNDINGBOX(#295,2442.68962272,328.607047524,39.9950827884); 304 | #297=IFCSHAPEREPRESENTATION(#294,'Box','BoundingBox',(#296)); 305 | #298=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Model',*,*,*,*,#14,$,.MODEL_VIEW.,$); 306 | #299=IFCCARTESIANPOINT((0.,0.)); 307 | #300=IFCCARTESIANPOINT((2443.99956516,0.)); 308 | #301=IFCPOLYLINE((#299,#300)); 309 | #302=IFCSHAPEREPRESENTATION(#298,'Axis','Curve2D',(#301)); 310 | #303=IFCPRODUCTDEFINITIONSHAPE($,$,(#293,#297,#302)); 311 | #304=IFCCOVERING('3es57B9Kr3nxL4uBITV$0e',#8,'dakopstand',$,$,#58,#303,'E8D851CB-254D-43C7-B544-E0B49D7FF028',$); 312 | ENDSEC; 313 | END-ISO-10303-21; 314 | -------------------------------------------------------------------------------- /timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_H 2 | #define TIMER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class timer { 11 | typedef std::map timings_map; 12 | std::mutex write_mutex; 13 | timings_map timings_; 14 | 15 | public: 16 | // @todo something like a scoped_stopwatch using RAII 17 | class stopwatch { 18 | typedef std::chrono::time_point timepoint; 19 | std::string key; 20 | timepoint start_, stop_; 21 | timings_map& timings; 22 | std::mutex& mutex; 23 | 24 | public: 25 | stopwatch(timings_map& timings, std::mutex& mutex) : timings(timings), mutex(mutex) {} 26 | stopwatch& start(const std::string& s) { 27 | key = s; 28 | start_ = std::chrono::steady_clock::now(); 29 | return *this; 30 | } 31 | void stop() { 32 | std::lock_guard lk(mutex); 33 | stop_ = std::chrono::steady_clock::now(); 34 | timings[key] += std::chrono::duration(stop_ - start_).count(); 35 | } 36 | }; 37 | 38 | static timer& i() { 39 | static timer t; 40 | return t; 41 | } 42 | 43 | static stopwatch measure(const std::string& s) { 44 | return stopwatch(i().timings_, i().write_mutex).start(s); 45 | } 46 | 47 | static void print(std::ostream& s) { 48 | size_t max_key_length = 0; 49 | for (auto& p : i().timings_) { 50 | if (p.first.size() > max_key_length) { 51 | max_key_length = p.first.size(); 52 | } 53 | } 54 | for (auto& p : i().timings_) { 55 | s << p.first << std::string(max_key_length - p.first.size(), ' ') << ":" << p.second << std::endl; 56 | } 57 | } 58 | }; 59 | 60 | #endif -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace util { 9 | 10 | namespace copy { 11 | 12 | template 13 | typename std::enable_if::value>::type polyhedron(Poly_B& poly_b, const Poly_A& poly_a) { 14 | poly_b = poly_a; 15 | } 16 | 17 | template 18 | typename std::enable_if::value>::type polyhedron(Poly_B& poly_b, const Poly_A& poly_a) { 19 | poly_b.clear(); 20 | CGAL::copy_face_graph(poly_a, poly_b); 21 | } 22 | 23 | template 24 | typename std::enable_if::value>::type transformation(CGAL::Aff_transformation_3& trsf_b, const CGAL::Aff_transformation_3& trsf_a) { 25 | trsf_b = trsf_a; 26 | } 27 | 28 | template 29 | typename std::enable_if::value>::type transformation(CGAL::Aff_transformation_3& trsf_b, const CGAL::Aff_transformation_3& trsf_a) { 30 | CGAL::NT_converter< 31 | typename Ka::RT, 32 | typename Kb::RT> converter; 33 | trsf_b = CGAL::Aff_transformation_3( 34 | converter(trsf_a.hm(0, 0)), 35 | converter(trsf_a.hm(0, 1)), 36 | converter(trsf_a.hm(0, 2)), 37 | converter(trsf_a.hm(0, 3)), 38 | converter(trsf_a.hm(1, 0)), 39 | converter(trsf_a.hm(1, 1)), 40 | converter(trsf_a.hm(1, 2)), 41 | converter(trsf_a.hm(1, 3)), 42 | converter(trsf_a.hm(2, 0)), 43 | converter(trsf_a.hm(2, 1)), 44 | converter(trsf_a.hm(2, 2)), 45 | converter(trsf_a.hm(2, 3)), 46 | converter(trsf_a.hm(3, 3)) 47 | ); 48 | } 49 | 50 | } 51 | 52 | template 53 | class PolyFromMesh : public CGAL::Modifier_base { 54 | private: 55 | 56 | std::list points_; 57 | std::vector> indices_; 58 | 59 | public: 60 | PolyFromMesh(const std::list& points, const std::vector>& indices) 61 | : points_(points) 62 | , indices_(indices) {} 63 | 64 | void operator()(HDS& hds) { 65 | CGAL::Polyhedron_incremental_builder_3 B(hds, false); 66 | 67 | B.begin_surface(points_.size(), indices_.size()); 68 | 69 | for (auto& p : points_) { 70 | B.add_vertex(p); 71 | } 72 | 73 | for (auto& fs : indices_) { 74 | B.begin_facet(); 75 | for (auto& i : fs) { 76 | B.add_vertex_to_facet(i); 77 | } 78 | B.end_facet(); 79 | } 80 | 81 | B.end_surface(); 82 | } 83 | }; 84 | 85 | } 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /writer.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tudelft3d/esri_geobim/5b3ac72dd722b0fc6f148f3c5cb9dea8d4caa76a/writer.cpp -------------------------------------------------------------------------------- /writer.h: -------------------------------------------------------------------------------- 1 | #ifndef WRITER_H 2 | #define WRITER_H 3 | 4 | #include "processing.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | template 14 | struct vertex_cache { 15 | std::map vertex_indices; 16 | std::vector

vertex_points; 17 | }; 18 | 19 | struct print_and_clear_point_cache { 20 | std::ostream& obj_; 21 | 22 | print_and_clear_point_cache(std::ostream& obj) 23 | : obj_(obj) 24 | {} 25 | 26 | void operator()(boost::blank&) {} 27 | 28 | template 29 | void operator()(T& c) { 30 | for (auto& p : c.vertex_points) { 31 | obj_ << "v " 32 | << p.cartesian(0) << " " 33 | << p.cartesian(1) << " " 34 | << p.cartesian(2) << "\n"; 35 | } 36 | c.vertex_points.clear(); 37 | } 38 | }; 39 | 40 | // Abstract writer class that takes triangular facets. 41 | struct abstract_writer { 42 | typedef CGAL::Simple_cartesian::Point_3 P3; 43 | std::vector* point_lookup; 44 | 45 | boost::variant, vertex_cache> cache; 46 | 47 | std::array points_from_facet(std::vector>::const_iterator f) { 48 | return { 49 | (*point_lookup)[(*f)[0]], 50 | (*point_lookup)[(*f)[1]], 51 | (*point_lookup)[(*f)[2]] 52 | }; 53 | } 54 | 55 | std::array points_from_facet(std::list>::const_iterator>::const_iterator f) { 56 | return points_from_facet(*f); 57 | } 58 | 59 | std::array points_from_facet(cgal_shape_t::Facet_handle f) { 60 | return { 61 | f->facet_begin()->vertex()->point(), 62 | f->facet_begin()->next()->vertex()->point(), 63 | f->facet_begin()->next()->next()->vertex()->point() 64 | }; 65 | } 66 | 67 | std::array points_from_facet(CGAL::Polyhedron_3>::Facet_handle f) { 68 | return { 69 | f->facet_begin()->vertex()->point(), 70 | f->facet_begin()->next()->vertex()->point(), 71 | f->facet_begin()->next()->next()->vertex()->point() 72 | }; 73 | } 74 | 75 | std::array point_indices_from_facet(std::vector>::const_iterator f) { 76 | return { 77 | (*f)[0], 78 | (*f)[1], 79 | (*f)[2] 80 | }; 81 | } 82 | 83 | std::array point_indices_from_facet(std::list>::const_iterator>::const_iterator f) { 84 | return point_indices_from_facet(*f); 85 | } 86 | 87 | template 88 | std::array point_indices_from_facet(T t) { 89 | auto arr = points_from_facet(t); 90 | typedef typename decltype(arr)::value_type U; 91 | if (cache.which() == 0) { 92 | cache = vertex_cache(); 93 | } 94 | auto& C = boost::get>(cache); 95 | std::array idxs; 96 | for (int i = 0; i < 3; ++i) { 97 | auto it = C.vertex_indices.find(arr[i]); 98 | if (it == C.vertex_indices.end()) { 99 | auto n = C.vertex_indices.size(); 100 | idxs[i] = C.vertex_indices[arr[i]] = n; 101 | C.vertex_points.push_back(arr[i]); 102 | } 103 | else { 104 | idxs[i] = it->second; 105 | } 106 | } 107 | return idxs; 108 | } 109 | 110 | std::array points_from_facet(std::list::iterator f) { 111 | return points_from_facet(*f); 112 | } 113 | 114 | bool has_finalized = false; 115 | virtual void do_finalize() = 0; 116 | virtual ~abstract_writer() { 117 | } 118 | void finalize() { 119 | if (!has_finalized) { 120 | has_finalized = true; 121 | do_finalize(); 122 | } 123 | } 124 | }; 125 | 126 | // OBJ writer for CGAL facets paired with a style 127 | struct simple_obj_writer : public abstract_writer { 128 | int group_id = 1; 129 | int vertex_count = 1; 130 | std::ofstream obj, mtl; 131 | rgb GRAY; 132 | 133 | simple_obj_writer(const std::string& fn_prefix) 134 | : obj((fn_prefix + ".obj").c_str()) 135 | , mtl((fn_prefix + ".mtl").c_str()) 136 | , GRAY(0.6, 0.6, 0.6) { 137 | obj << "mtllib " << fn_prefix << ".mtl\n"; 138 | } 139 | 140 | // @todo make the Kernel or Point_3 type discoverable from this template 141 | template 142 | void operator()(const item_info* info, It begin, It end) { 143 | const auto& diffuse = info && info->diffuse ? *info->diffuse : GRAY; 144 | 145 | obj << "g " << (info ? info->guid : "unknown") << "\n"; 146 | obj << "usemtl m" << group_id << "\n"; 147 | mtl << "newmtl m" << group_id << "\n"; 148 | mtl << "kd " << diffuse.r() << " " << diffuse.g() << " " << diffuse.b() << "\n"; 149 | 150 | group_id++; 151 | 152 | /* 153 | for (auto it = begin; it != end; ++it) { 154 | auto points = points_from_facet(it); 155 | for (int i = 0; i < 3; ++i) { 156 | obj << "v " 157 | << points[i].cartesian(0) << " " 158 | << points[i].cartesian(1) << " " 159 | << points[i].cartesian(2) << "\n"; 160 | } 161 | obj << "f " 162 | << (vertex_count + 0) << " " 163 | << (vertex_count + 1) << " " 164 | << (vertex_count + 2) << "\n"; 165 | vertex_count += 3; 166 | } 167 | */ 168 | 169 | std::vector> fs; 170 | for (auto it = begin; it != end; ++it) { 171 | fs.push_back(point_indices_from_facet(it)); 172 | } 173 | 174 | boost::apply_visitor(print_and_clear_point_cache(obj), cache); 175 | 176 | for (auto& f : fs) { 177 | obj << "f " 178 | << f[0] + 1 << " " 179 | << f[1] + 1 << " " 180 | << f[2] + 1 << "\n"; 181 | } 182 | } 183 | 184 | void do_finalize() {} 185 | }; 186 | 187 | namespace { 188 | 189 | struct predicate_always { 190 | bool operator()(const Eigen::Vector3d&) const { 191 | return true; 192 | } 193 | }; 194 | 195 | struct predicate_is_up { 196 | bool operator()(const Eigen::Vector3d& norm) const { 197 | return norm(2) > 0.; 198 | } 199 | }; 200 | 201 | std::string map_semantics(const std::string& ifc, const Eigen::Vector3d& norm) { 202 | static predicate_always always; 203 | static predicate_is_up is_up; 204 | 205 | static std::vector>, std::string>> mappings { 206 | {{"IfcSlab", is_up}, "RoofSurface"}, 207 | {{"IfcSlab", always}, "GroundSurface"}, 208 | {{"IfcWall", always}, "WallSurface"}, 209 | {{"IfcWindow", always}, "Window"}, 210 | {{"IfcDoor", always}, "Door"}, 211 | }; 212 | 213 | for (auto& m : mappings) { 214 | if (m.first.first == ifc && m.first.second(norm)) { 215 | return m.second; 216 | } 217 | } 218 | 219 | return "ClosureSurface"; 220 | }; 221 | } 222 | 223 | struct city_json_writer : public abstract_writer { 224 | rgb GRAY; 225 | 226 | using json = nlohmann::json; 227 | 228 | std::string filename; 229 | 230 | std::vector> vertices; 231 | std::vector>>> boundaries; 232 | std::vector> boundary_materials; 233 | std::vector boundary_semantics; 234 | std::vector boundary_semantics_values; 235 | 236 | json materials; 237 | 238 | city_json_writer(const std::string& fn_prefix) 239 | : filename(fn_prefix + ".json") 240 | , materials(json::array()) 241 | , GRAY(0.6, 0.6, 0.6) 242 | { 243 | boundaries.emplace_back(); 244 | boundary_materials.emplace_back(); 245 | } 246 | 247 | template 248 | void operator()(const item_info* info, It begin, It end) { 249 | const auto& diffuse = info && info->diffuse ? *info->diffuse : GRAY; 250 | 251 | json material = json::object(); 252 | material["name"] = "material-" + boost::lexical_cast(materials.size()); 253 | material["diffuseColor"] = std::array{diffuse.r(), diffuse.g(), diffuse.b()}; 254 | material["specularColor"] = std::array{0., 0., 0.}; 255 | material["shininess"] = 0.; 256 | material["isSmooth"] = false; 257 | materials.push_back(material); 258 | 259 | for (auto it = begin; it != end; ++it) { 260 | auto points = points_from_facet(it); 261 | std::vector faces; 262 | for (int i = 0; i < 3; ++i) { 263 | faces.push_back(vertices.size()); 264 | vertices.push_back({ { 265 | CGAL::to_double(points[i].cartesian(0)), 266 | CGAL::to_double(points[i].cartesian(1)), 267 | CGAL::to_double(points[i].cartesian(2)) 268 | } }); 269 | } 270 | 271 | Eigen::Vector3d a, b, c; 272 | a << vertices[faces[0]][0], vertices[faces[0]][1], vertices[faces[0]][2]; 273 | b << vertices[faces[1]][0], vertices[faces[1]][1], vertices[faces[1]][2]; 274 | c << vertices[faces[2]][0], vertices[faces[2]][1], vertices[faces[2]][2]; 275 | Eigen::Vector3d norm = (b - a).cross(c - a); 276 | 277 | boundaries.back().push_back({ faces }); 278 | boundary_materials.back().push_back(materials.size() - 1); 279 | json json_type = json::object(); 280 | json_type["type"] = map_semantics(info ? info->entity_type : "x", norm); 281 | boundary_semantics.push_back(json_type); 282 | boundary_semantics_values.push_back(boundary_semantics_values.size()); 283 | } 284 | } 285 | 286 | void do_finalize() { 287 | json city; 288 | 289 | city["type"] = "CityJSON"; 290 | city["version"] = "1.0"; 291 | city["extensions"] = json::object(); 292 | city["metadata"]["referenceSystem"] = "urn:ogc:def:crs:EPSG::2355"; 293 | city["vertices"] = vertices; 294 | city["appearance"]["materials"] = materials; 295 | 296 | auto& building1 = city["CityObjects"]["id-1"]; 297 | building1["type"] = "Building"; 298 | building1["geographicalExtent"] = std::array{0, 0, 0, 1, 1, 1}; 299 | 300 | json geom = json::object(); 301 | geom["type"] = "Solid"; 302 | geom["lod"] = 2; 303 | geom["boundaries"] = boundaries; 304 | geom["semantics"]["values"][0] = boundary_semantics_values; 305 | geom["semantics"]["surfaces"] = boundary_semantics; 306 | geom["material"]["diffuse"]["values"] = boundary_materials; 307 | building1["geometry"].push_back(geom); 308 | 309 | std::ofstream(filename.c_str()) << city; 310 | } 311 | }; 312 | 313 | struct external_element_collector : public abstract_writer { 314 | using json = nlohmann::json; 315 | 316 | std::string filename; 317 | const std::list& all_infos; 318 | std::set part_of_exterior; 319 | 320 | 321 | external_element_collector(const std::string& fn_prefix, const std::list& infos) 322 | : filename(fn_prefix + ".json") 323 | , all_infos(infos) 324 | {} 325 | 326 | template 327 | void operator()(const item_info* info, It begin, It end) { 328 | if (info) { 329 | part_of_exterior.insert(info); 330 | } 331 | } 332 | 333 | void do_finalize() { 334 | json data = json::array(); 335 | 336 | for (auto& info : all_infos) { 337 | json object = json::object(); 338 | object["guid"] = info->guid; 339 | object["is_external"] = part_of_exterior.find(info) != part_of_exterior.end(); 340 | 341 | data.push_back(object); 342 | } 343 | 344 | std::ofstream(filename.c_str()) << data; 345 | } 346 | }; 347 | 348 | #endif 349 | --------------------------------------------------------------------------------