From 9e19735275f89c56187df572ac5b4568dec1ca2c Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sun, 19 Apr 2026 12:32:10 +0200 Subject: [PATCH] IfcConvert Plug-in discovery for info print --- src/ifcconvert/CMakeLists.txt | 5 +- src/ifcconvert/IfcConvert.cpp | 156 +++-- src/ifcconvert/validate_space_boundaries.cpp | 186 ++++++ .../validate_storey_containment.cpp | 238 +++++++ src/ifcconvert/validate_wall_connectivity.cpp | 201 ++++++ src/ifcconvert/validation_utils.cpp | 32 + src/ifcconvert/validation_utils.h | 602 ++++++++++++++++++ src/serializers/CMakeLists.txt | 14 +- src/serializers/document_rdb_plugin.cpp | 2 + .../document_serializer_plugin.cpp | 6 + src/serializers/document_serializer_plugin.h | 2 + src/serializers/geometry_dae_plugin.cpp | 2 + src/serializers/geometry_glb_plugin.cpp | 2 + src/serializers/geometry_hdf_plugin.cpp | 2 + src/serializers/geometry_igs_plugin.cpp | 2 + src/serializers/geometry_obj_plugin.cpp | 2 + .../geometry_serializer_plugin.cpp | 6 + src/serializers/geometry_serializer_plugin.h | 2 + src/serializers/geometry_stp_plugin.cpp | 2 + src/serializers/geometry_svg_plugin.cpp | 2 + src/serializers/geometry_ttl_plugin.cpp | 2 + src/serializers/geometry_usd_plugin.cpp | 2 + .../schema_dependent/CMakeLists.txt | 2 +- .../schema_dependent/json_plugin.cpp | 2 + .../schema_dependent/xml_plugin.cpp | 2 + 25 files changed, 1423 insertions(+), 53 deletions(-) create mode 100644 src/ifcconvert/validate_space_boundaries.cpp create mode 100644 src/ifcconvert/validate_storey_containment.cpp create mode 100644 src/ifcconvert/validate_wall_connectivity.cpp create mode 100644 src/ifcconvert/validation_utils.cpp create mode 100644 src/ifcconvert/validation_utils.h diff --git a/src/ifcconvert/CMakeLists.txt b/src/ifcconvert/CMakeLists.txt index 603f1538c2..a60eb17e76 100644 --- a/src/ifcconvert/CMakeLists.txt +++ b/src/ifcconvert/CMakeLists.txt @@ -1,10 +1,7 @@ # IfcConvert add_executable(IfcConvert IfcConvert.cpp) -target_link_libraries(IfcConvert PRIVATE IfcGeom IfcParse ${OpenCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES}) -if(TARGET geometry_serializer_hdf) - target_link_libraries(IfcConvert PRIVATE geometry_serializer_hdf) -endif() +target_link_libraries(IfcConvert PRIVATE IfcGeom IfcParse ${OpenCASCADE_LIBRARIES} ${Boost_LIBRARIES}) if(kernel_libraries OR mapping_libraries OR geometry_serializer_libraries OR document_serializer_libraries) add_dependencies(IfcConvert ${kernel_libraries} ${mapping_libraries} ${geometry_serializer_libraries} ${document_serializer_libraries}) endif() diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 77560c50d9..50263a654f 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -50,11 +50,13 @@ #include #include +#include #include -#include -#include -#include #include +#include +#include +#include +#include #if USE_VLD #include @@ -85,6 +87,91 @@ const std::string TEMP_FILE_EXTENSION = ".tmp"; namespace po = boost::program_options; +namespace { + +struct serializer_usage_line { + std::string extensions; + std::string name; + std::string description; +}; + +std::string join_extensions(const std::vector& extensions) { + std::ostringstream stream; + for (std::size_t i = 0; i < extensions.size(); ++i) { + if (i != 0) { + stream << ", "; + } + stream << extensions[i]; + } + return stream.str(); +} + +void print_serializer_section(const char* title, std::vector lines) { + if (lines.empty()) { + return; + } + + std::size_t extensions_width = 0; + std::size_t name_width = 0; + for (const auto& line : lines) { + extensions_width = std::max(extensions_width, line.extensions.size()); + name_width = std::max(name_width, line.name.size()); + } + + cout_ << title << "\n"; + for (const auto& line : lines) { + const auto extensions = ifcopenshell::path::from_utf8(line.extensions); + const auto name = ifcopenshell::path::from_utf8(line.name); + const auto description = ifcopenshell::path::from_utf8(line.description); + cout_ << " " + << std::left << std::setw(static_cast(extensions_width + 2)) << extensions + << std::setw(static_cast(name_width + 2)) << name + << description << "\n"; + } + cout_ << "\n"; +} + +std::vector geometry_serializer_usage_lines() { + auto serializers = ifcopenshell::serializers::geometry_serializer_registry_instance().serializers(); + std::sort(serializers.begin(), serializers.end(), [](const auto& a, const auto& b) { + const auto& lhs = a.extensions.empty() ? a.format : a.extensions.front(); + const auto& rhs = b.extensions.empty() ? b.format : b.extensions.front(); + if (lhs != rhs) { + return lhs < rhs; + } + return a.format < b.format; + }); + + std::vector lines; + lines.reserve(serializers.size()); + for (const auto& info : serializers) { + lines.push_back({ join_extensions(info.extensions), info.name, info.description }); + } + return lines; +} + +std::vector document_serializer_usage_lines() { + auto serializers = ifcopenshell::serializers::document_serializer_registry_instance().serializers(); + std::sort(serializers.begin(), serializers.end(), [](const auto& a, const auto& b) { + if (a.format != b.format) { + return a.format < b.format; + } + return a.schema_name < b.schema_name; + }); + + std::set seen_formats; + std::vector lines; + for (const auto& info : serializers) { + if (!seen_formats.insert(info.format).second) { + continue; + } + lines.push_back({ "." + info.format, info.name, info.description }); + } + return lines; +} + +} + void print_version() { cout_ << "IfcOpenShell IfcConvert " << IFCOPENSHELL_VERSION; @@ -98,31 +185,11 @@ void print_usage(bool suggest_help = true) { cout_ << "Usage: IfcConvert [options] []\n" << "\n" - << "Converts (the geometry in) an IFC file into one of the following formats:\n" - << " .obj WaveFront OBJ (a .mtl file is also created)\n" -#ifdef WITH_OPENCOLLADA - << " .dae Collada Digital Assets Exchange\n" -#endif -#ifdef WITH_GLTF - << " .glb glTF Binary glTF v2.0\n" -#endif -#ifdef WITH_USD - << " .usd USD Universal Scene Description\n" -#endif - << " .stp STEP Standard for the Exchange of Product Data\n" - << " .igs IGES Initial Graphics Exchange Specification\n" - << " .xml XML Property definitions and decomposition tree\n" -#ifdef WITH_GLTF - << " .json JSON Property definitions and decomposition tree in xeokit json format\n" -#endif - << " .rdb RocksDB RocksDB Key-Value store serialization of IFC data\n" - << " .svg SVG Scalable Vector Graphics (2D floor plan)\n" -#ifdef WITH_HDF5 - << " .h5 HDF Hierarchical Data Format storing positions, normals and indices\n" -#endif - << " .ttl TTL/WKT RDF Turtle with Well-Known-Text geometry\n" - << " .ifc IFC-SPF Industry Foundation Classes\n" - << "\n" + << "Converts (the geometry in) an IFC file into one of the following formats:\n\n"; + print_serializer_section("Geometry serializers:", geometry_serializer_usage_lines()); + print_serializer_section("Document serializers:", document_serializer_usage_lines()); + print_serializer_section("Built-in:", { { ".ifc", "IFC-SPF", "Industry Foundation Classes." } }); + cout_ << "If no output filename given, " << ifcopenshell::path::from_utf8(DEFAULT_EXTENSION) << " will be used as the output file.\n"; if (suggest_help) { cout_ << "\nRun 'IfcConvert --help' for more information."; @@ -224,6 +291,13 @@ int main(int argc, char** argv) { path_t cache_file; std::string log_format; std::string geometry_kernel; + auto& document_serializer_registry = ifcopenshell::serializers::document_serializer_registry_instance(); + auto& geometry_serializer_registry = ifcopenshell::serializers::geometry_serializer_registry_instance(); +#ifdef WITH_HDF5 + const bool supports_geometry_cache = geometry_serializer_registry.has(".h5"); +#else + const bool supports_geometry_cache = false; +#endif po::options_description generic_options("Command line options"); verbosity_counter vcounter; @@ -232,14 +306,15 @@ int main(int argc, char** argv) { ("version", "display version information") ("verbose,v", po::value(&vcounter)->zero_tokens(), "more verbose log messages. Use twice (-vv) for debugging level.") ("quiet,q", "less status and progress output") -#ifdef WITH_HDF5 - ("cache", "cache geometry creation. Use --cache-file to specify cache file path.") -#endif ("stderr-progress", "output progress to stderr stream") ("yes,y", "answer 'yes' automatically to possible confirmation queries (e.g. overwriting an existing output file)") ("no-progress", "suppress possible progress bar type of prints that use carriage return") ("log-format", po::value(&log_format), "log format: plain or json") ("log-file", new po::typed_value(&log_file), "redirect log output to file"); + if (supports_geometry_cache) { + generic_options.add_options() + ("cache", "cache geometry creation. Use --cache-file to specify cache file path."); + } po::options_description fileio_options; fileio_options.add_options() @@ -248,11 +323,12 @@ int main(int argc, char** argv) { #endif ("input-file", new po::typed_value(0), "input IFC file") ("output-file", new po::typed_value(0), "output geometry file") -#ifdef WITH_HDF5 - ("cache-file", new po::typed_value(&cache_file), "geometry cache file") -#endif ("stream", "Use streaming conversion (currently supported with conversion to RocksDB)") ; + if (supports_geometry_cache) { + fileio_options.add_options() + ("cache-file", new po::typed_value(&cache_file), "geometry cache file"); + } po::options_description ifc_options("IFC options"); ifc_options.add_options() @@ -534,11 +610,8 @@ int main(int argc, char** argv) { boost::optional> elems_from_adaptor; - const path_t CACHE = ifcopenshell::path::from_utf8(".cache"), - HDF = ifcopenshell::path::from_utf8(".h5"), - IFC = ifcopenshell::path::from_utf8(".ifc"); + const path_t IFC = ifcopenshell::path::from_utf8(".ifc"); - auto& document_serializer_registry = ifcopenshell::serializers::document_serializer_registry_instance(); const auto* document_serializer_info = document_serializer_registry.find(output_extension_utf8); if (document_serializer_info) { int exit_code = EXIT_FAILURE; @@ -613,7 +686,6 @@ int main(int argc, char** argv) { return exit_code; } - auto& geometry_serializer_registry = ifcopenshell::serializers::geometry_serializer_registry_instance(); const auto* geometry_serializer_info = geometry_serializer_registry.find(output_extension_utf8); if (!geometry_serializer_info) { cerr_ << "[error] Unknown output filename extension '" << output_extension << "'\n"; @@ -830,10 +902,12 @@ int main(int argc, char** argv) { context_iterator.reset(new IfcGeom::Iterator(ifcopenshell::geometry::kernels::construct(ifc_file, geometry_kernel, geometry_settings), geometry_settings, ifc_file, filter_funcs, num_threads)); } -#if defined(WITH_HDF5) && defined(IFOPSH_WITH_OPENCASCADE) +#ifdef WITH_HDF5 boost::shared_ptr cache; - const bool use_cache = context_iterator && (vmap.count("cache-file") || vmap.count("cache")); + const bool use_cache = supports_geometry_cache && context_iterator && (vmap.count("cache-file") || vmap.count("cache")); if (use_cache) { + const path_t CACHE = ifcopenshell::path::from_utf8(".cache"); + const path_t HDF = ifcopenshell::path::from_utf8(".h5"); if (!vmap.count("cache-file")) { cache_file = input_filename + CACHE + HDF; } diff --git a/src/ifcconvert/validate_space_boundaries.cpp b/src/ifcconvert/validate_space_boundaries.cpp new file mode 100644 index 0000000000..a26c963e7f --- /dev/null +++ b/src/ifcconvert/validate_space_boundaries.cpp @@ -0,0 +1,186 @@ +#ifdef IFOPSH_WITH_CGAL + +#include "validation_utils.h" + +using namespace ifcopenshell::geometry; + +#include +#include +#include +#include + +typedef Kernel_::FT FT; +typedef Kernel_::Point_3 Point; +typedef Kernel_::Segment_3 Segment; +typedef CGAL::Polyhedron_3 Polyhedron; +typedef CGAL::AABB_face_graph_triangle_primitive Primitive; +typedef CGAL::AABB_traits Traits; +typedef CGAL::AABB_tree Tree; +typedef Tree::Point_and_primitive_id Point_and_primitive_id; + +void fix_spaceboundaries(ifcopenshell::file& f, bool no_progress, bool quiet, bool stderr_progress) { + intersection_validator v(f, { "IfcWall", "IfcSpace", "IfcSlab", "IfcCovering" }, 1.e-5, no_progress, quiet, stderr_progress); + + auto rels = f.instances_by_type("IfcRelSpaceBoundary"); + + std::map, const ifcopenshell::IfcBaseClass*> rel_by_space_elem; + + + if (rels) { + std::for_each(rels->begin(), rels->end(), [&rel_by_space_elem](const ifcopenshell::IfcBaseClass* rel) { + auto x = ((ifcopenshell::IfcBaseEntity*)rel)->get_value("RelatingSpace"); + try { + auto y = ((ifcopenshell::IfcBaseEntity*)rel)->get_value("RelatedBuildingElement"); + rel_by_space_elem.insert({ { x,y }, rel }); + } catch (ifcopenshell::exception&) { + // RelatedBuildingElement can be NULL + } + }); + } + + std::set rels_encounted; + + ifcopenshell::file f2("boundaries-triangulated.ifc"); + if (!f2.good()) { + return; + } + + ifcopenshell::geometry::Settings settings; + + settings.get().value = false; + settings.get().value = false; + settings.get().value = true; + settings.get().value = true; + settings.get().value = ifcopenshell::geometry::settings::NATIVE; + settings.get().value = true; + + ifcopenshell::geometry::Converter c("cgal", &f2, settings); + + std::map, std::vector> elem_to_space_boundary_coords; + + for (auto& i : *f2.instances_by_type("IfcProduct")) { + auto n = ((ifcopenshell::IfcBaseEntity*)i)->get_value("Name"); + auto g1 = n.substr(0, 22); + auto g2 = n.substr(23); + auto item = c.mapping()->map(i); + if (taxonomy::cast(item)->children[0] == nullptr) { + continue; + } + auto shell = taxonomy::cast(taxonomy::cast(taxonomy::cast(item)->children[0])->children[0]); + for (auto& face : shell->children) { + for (auto& wire : face->children) { + for (auto& edge : wire->children) { + auto p3 = boost::get(edge->start); + auto p4 = taxonomy::cast(item)->matrix->ccomponents() * p3->ccomponents().homogeneous(); + Kernel_::Point_3 P(p4(0), p4(1), p4(2)); + elem_to_space_boundary_coords[{g1, g2}].emplace_back(P); + } + } + } + } + + std::set< std::set > guid_pairs_visited; + + v([&rel_by_space_elem, &elem_to_space_boundary_coords, &guid_pairs_visited](const intersection_validator::Box& a, const intersection_validator::Box& b) { + std::ostringstream ss; + // ss << id_map[a.id()]->first->data().to_string() << "x" << id_map[b.id()]->first->data().to_string() << std::endl; + // auto x = id_map[a.id()]->second * id_map[b.id()]->second; + + auto A = a.handle()->first; + auto B = b.handle()->first; + + auto Aguid = A->get_value("GlobalId"); + auto Bguid = B->get_value("GlobalId"); + + int space_count = 0; + if (A->declaration().name() == "IfcSpace") { + space_count += 1; + } + if (B->declaration().name() == "IfcSpace") { + space_count += 1; + } + if (space_count != 1) { + return; + } + + ss << a.handle()->first->data().to_string() << "x" << a.handle()->first->data().to_string() << std::endl; + auto x = a.handle()->second * b.handle()->second; + + if (x.is_empty()) { + return; + } + + guid_pairs_visited.insert({ Aguid, Bguid }); + + cgal_shape_t x_poly; + x.convert_to_polyhedron(x_poly); + + { + std::string fn = "computed_boundaries_" + Aguid + "_" + Bguid + ".off"; + std::ofstream computed_boundaries(fn.c_str()); + computed_boundaries.precision(17); + computed_boundaries << x_poly; + } + + Tree tree(faces(x_poly).first, faces(x_poly).second, x_poly); + tree.accelerate_distance_queries(); + + auto itelem = elem_to_space_boundary_coords.find({ Aguid, Bguid }); + + if (itelem == elem_to_space_boundary_coords.end()) { + logger::error("Missing space boundary relationship " + Aguid + " " + Bguid); + return; + } + + const auto& coords = itelem->second; + std::vector distances; + std::transform(coords.begin(), coords.end(), std::back_inserter(distances), [&tree](const Kernel_::Point_3& p) { + return std::sqrt(CGAL::to_double(tree.squared_distance(p))); + }); + + bool valid = *std::max_element(distances.begin(), distances.end()) < 0.4; + + if (!valid) { + logger::error("Wrong connection geometry " + Aguid + " " + Bguid); + } + + /*{ + remove_thickness r(x_poly); + std::string fn = "thin_computed_boundaries_" + Aguid + "_" + Bguid + ".off"; + std::ofstream computed_boundaries(fn.c_str()); + computed_boundaries.precision(17); + computed_boundaries << r.flattened; + }*/ + + /* + { + auto FN = s0 + "-" + s1 + "-" + std::to_string(i0) + "-" + std::to_string(i1) + "-sides-sb.off"; + std::ofstream os(FN.c_str()); + os.precision(17); + os << r.polyhedron2; + } + { + auto FN = s0 + "-" + s1 + "-" + std::to_string(i0) + "-" + std::to_string(i1) + "-flat-sb.off"; + std::ofstream os(FN.c_str()); + os.precision(17); + os << r.flattened; + } + */ + }); + + auto is_wall_space_or_slab = [&f](const std::string& g) { + auto decl = f.instance_by_guid(g)->declaration(); + return decl.is("IfcWall") || decl.is("IfcSpace") || decl.is("IfcSlab"); + }; + + for (auto& i : *f2.instances_by_type("IfcProduct")) { + auto n = ((ifcopenshell::IfcBaseEntity*)i)->get_value("Name"); + auto g1 = n.substr(0, 22); + auto g2 = n.substr(23); + if (is_wall_space_or_slab(g1) && is_wall_space_or_slab(g2) && guid_pairs_visited.find({ g1, g2 }) == guid_pairs_visited.end()) { + logger::error("Space boundary for non-bounding geometry " + g1 + " " + g2); + } + } +} + +#endif diff --git a/src/ifcconvert/validate_storey_containment.cpp b/src/ifcconvert/validate_storey_containment.cpp new file mode 100644 index 0000000000..39c5df0176 --- /dev/null +++ b/src/ifcconvert/validate_storey_containment.cpp @@ -0,0 +1,238 @@ +#ifdef IFOPSH_WITH_CGAL + +#include "../ifcgeom/kernels/cgal/CgalKernel.h" +#include "../ifcgeom/IfcGeomFilter.h" +#include "../ifcgeom/Iterator.h" + +#include +#include + +#include + +void fix_storeycontainment(ifcopenshell::file& f, bool no_progress, bool quiet, bool stderr_progress) { + ifcopenshell::geometry::Settings settings; + + settings.get().value = false; + settings.get().value = false; + settings.get().value = true; + settings.get().value = true; + settings.get().value = ifcopenshell::geometry::settings::NATIVE; + settings.get().value = true; + + std::vector no_openings_and_spaces = { + IfcGeom::entity_filter(false, false, {"IfcOpeningElement", "IfcSpace"}) + }; + + IfcGeom::Iterator context_iterator("cgal", settings, &f, no_openings_and_spaces, 1); + + auto get_elevation = [](const ifcopenshell::IfcBaseClass* a) { + return ((const ifcopenshell::IfcBaseEntity*)a)->get_value("Elevation", 0.); + }; + + // latebound inverse attribute lookup not working + auto rels = f.instances_by_type("IfcRelContainedInSpatialStructure"); + std::map elem_to_storey; + std::for_each(rels->begin(), rels->end(), [&elem_to_storey](ifcopenshell::IfcBaseClass* r) { + auto elems = ((ifcopenshell::IfcBaseEntity*)r)->get_value("RelatedElements"); + auto storey = ((ifcopenshell::IfcBaseEntity*)r)->get_value("RelatingStructure"); + + if (storey->declaration().name() == "IfcBuildingStorey") { + for (auto it = elems->begin(); it != elems->end(); ++it) { + elem_to_storey[*it] = storey; + } + } + }); + + auto storeys = f.instances_by_type("IfcBuildingStorey"); + std::vector storeys_sorted(storeys->begin(), storeys->end()); + std::sort(storeys_sorted.begin(), storeys_sorted.end(), [&get_elevation](const ifcopenshell::IfcBaseClass* a, const ifcopenshell::IfcBaseClass* b) { + return get_elevation(a) < get_elevation(b); + }); + + /* + std::wcout << "Storeys "; + for (auto& s : storeys_sorted) { + auto n = ((ifcopenshell::IfcBaseEntity*)s)->get_value("Name"); + std::wcout << n.c_str() << " "; + } + std::wcout << std::endl; + */ + + std::vector elevations; + std::transform(storeys_sorted.begin(), storeys_sorted.end(), std::back_inserter(elevations), get_elevation); + + double LARGE = 1e4; + + std::vector> elevation_slices; + for (size_t i = 0; i < elevations.size(); ++i) { + elevation_slices.push_back({ + i == 0 ? -LARGE : elevations[i], + i + 1 == elevations.size() ? LARGE : elevations[i + 1] + }); + } + + std::for_each(elevation_slices.begin(), elevation_slices.end(), [](std::pair& p) { + p.first -= 0.3; + p.second += 0.3; + }); + + std::vector> nefs; + std::transform(elevation_slices.begin(), elevation_slices.end(), std::back_inserter(nefs), [&LARGE](const std::pair& p) { + // std::wcout << p.first << " - " << p.second << std::endl; + Kernel_::Point_3 p1(-LARGE, -LARGE, p.first); + Kernel_::Point_3 p2(+LARGE, +LARGE, p.second); + auto poly = ifcopenshell::geometry::utils::create_cube(p1, p2); + return ifcopenshell::geometry::utils::create_nef_polyhedron(poly); + }); + + /* + for (auto& n : nefs) { + auto poly = ifcopenshell::geometry::utils::create_polyhedron(n); + auto bounds = CGAL::Polygon_mesh_processing::bbox_3(poly); + for (int i = 0; i < 3; ++i) { + std::wcout << bounds.min(i) << std::endl; + } + for (int i = 0; i < 3; ++i) { + std::wcout << bounds.max(i) << std::endl; + } + std::wcout << "---" << std::endl; + } + */ + + if (!context_iterator.initialize()) { + return; + } + + size_t num_created = 0; + int old_progress = quiet ? 0 : -1; + + for (;; ++num_created) { + bool has_more = true; + if (num_created) { + has_more = context_iterator.next(); + } + IfcGeom::BRepElement* geom_object = nullptr; + if (has_more) { + geom_object = context_iterator.get_native(); + } + if (!geom_object) { + break; + } + + /* + std::stringstream ss; + ss << geom_object->product()->data().to_string(); + auto sss = ss.str(); + std::wcout << sss.c_str() << std::endl; + */ + + if (elem_to_storey.find(geom_object->product()) == elem_to_storey.end()) { + // std::wcout << "not associated to storey" << std::endl; + continue; + } + + std::vector intersection_volumes(nefs.size()); + + for (auto& g : geom_object->geometry()) { + auto s = std::static_pointer_cast(g.Shape())->poly(); + const auto& m = g.Placement()->ccomponents(); + const auto& n = geom_object->transformation().data()->ccomponents(); + + const cgal_placement_t trsf( + m(0, 0), m(0, 1), m(0, 2), m(0, 3), + m(1, 0), m(1, 1), m(1, 2), m(1, 3), + m(2, 0), m(2, 1), m(2, 2), m(2, 3)); + + const cgal_placement_t trsf2( + n(0, 0), n(0, 1), n(0, 2), n(0, 3), + n(1, 0), n(1, 1), n(1, 2), n(1, 3), + n(2, 0), n(2, 1), n(2, 2), n(2, 3)); + + // Apply transformation + for (auto &vertex : vertices(s)) { + vertex->point() = vertex->point().transform(trsf).transform(trsf2); + } + + /* + { + auto bounds = CGAL::Polygon_mesh_processing::bbox_3(s); + for (int i = 0; i < 3; ++i) { + std::wcout << bounds.min(i) << std::endl; + } + for (int i = 0; i < 3; ++i) { + std::wcout << bounds.max(i) << std::endl; + } + std::wcout << "---" << std::endl; + } + */ + + CGAL::Nef_polyhedron_3 part_nef = ifcopenshell::geometry::utils::create_nef_polyhedron(s); + + if (!part_nef.is_simple()) { + // std::wcout << "not simple" << std::endl; + continue; + } + + std::vector::iterator accumulator = intersection_volumes.begin(); + std::for_each(nefs.begin(), nefs.end(), [&accumulator, &part_nef](const CGAL::Nef_polyhedron_3& storey_nef) { + auto poly = ifcopenshell::geometry::utils::create_polyhedron(part_nef * storey_nef); + CGAL::Polygon_mesh_processing::triangulate_faces(poly); + *accumulator += CGAL::to_double(CGAL::Polygon_mesh_processing::volume(poly)); + accumulator++; + }); + } + + /* + std::wcout << "volumes: "; + for (auto& v : intersection_volumes) { + std::wcout << v << " "; + } + std::wcout << std::endl; + */ + + auto calc_idx = std::max_element(intersection_volumes.begin(), intersection_volumes.end()) - intersection_volumes.begin(); + auto calc_overlap = intersection_volumes[calc_idx]; + auto assigned_idx = std::distance(storeys_sorted.begin(), std::find(storeys_sorted.begin(), storeys_sorted.end(), elem_to_storey[geom_object->product()])); + auto assigned_overlap = intersection_volumes[assigned_idx]; + if (calc_overlap > 0 && assigned_overlap < calc_overlap * 0.9) { + auto s = geom_object->product()->get_value("GlobalId"); + auto s1 = ((ifcopenshell::IfcBaseEntity*)storeys_sorted[calc_idx])->get_value("GlobalId"); + auto s2 = ((ifcopenshell::IfcBaseEntity*)elem_to_storey[geom_object->product()])->get_value("GlobalId"); + logger::error("Element " + s + " contained in " + s2 + " located on " + s1); + } + + if (!no_progress) { + if (quiet) { + const int progress = context_iterator.progress(); + for (; old_progress < progress; ++old_progress) { + std::cout << "."; + if (stderr_progress) + std::cerr << "."; + } + std::cout << std::flush; + if (stderr_progress) + std::cerr << std::flush; + } else { + const int progress = context_iterator.progress() / 2; + if (old_progress != progress) logger::progress_bar(progress); + old_progress = progress; + } + } + } + + if (!no_progress && quiet) { + for (; old_progress < 100; ++old_progress) { + std::cout << "."; + if (stderr_progress) + std::cerr << "."; + } + std::cout << std::flush; + if (stderr_progress) + std::cerr << std::flush; + } else { + logger::status("\rDone fixing space boundaries for " + boost::lexical_cast(num_created) + + " objects "); + } +} + +#endif diff --git a/src/ifcconvert/validate_wall_connectivity.cpp b/src/ifcconvert/validate_wall_connectivity.cpp new file mode 100644 index 0000000000..ba068a8bd9 --- /dev/null +++ b/src/ifcconvert/validate_wall_connectivity.cpp @@ -0,0 +1,201 @@ +#ifdef IFOPSH_WITH_CGAL + +#include "validation_utils.h" + +#include +#include + +#include + +using namespace ifcopenshell::geometry; + +void fix_wallconnectivity(ifcopenshell::file& f, bool no_progress, bool quiet, bool stderr_progress) { + intersection_validator v(f, { "IfcWall" }, 1.e-3, no_progress, quiet, stderr_progress); + + ifcopenshell::geometry::Settings settings; + + settings.get().value = false; + settings.get().value = false; + settings.get().value = true; + settings.get().value = true; + settings.get().value = ifcopenshell::geometry::settings::NATIVE; + settings.get().value = true; + + settings.get().value = true; + settings.get().value = false; + + ifcopenshell::geometry::Converter c("cgal", &f, settings); + + auto rels = f.instances_by_type("IfcRelConnectsPathElements"); + std::map, const ifcopenshell::IfcBaseClass*> rel_by_elem; + std::for_each(rels->begin(), rels->end(), [&rel_by_elem](const ifcopenshell::IfcBaseClass* rel) { + auto x = ((ifcopenshell::IfcBaseEntity*)rel)->get_value("RelatingElement"); + auto y = ((ifcopenshell::IfcBaseEntity*)rel)->get_value("RelatedElement"); + rel_by_elem.insert({{ x,y }, rel}); + }); + + std::set rels_encounted; + + double total_nef_intersection_time = 0.; + double conversion_to_poly = 0.; + + v([&c, &rel_by_elem, &rels_encounted, &total_nef_intersection_time, &conversion_to_poly](const intersection_validator::Box& a, const intersection_validator::Box& b) { + auto A = a.handle()->first; + auto B = b.handle()->first; + + const ifcopenshell::IfcBaseClass* rel = nullptr; + std::string a_type, b_type; + + auto rit = rel_by_elem.find({ A, B }); + if (rit != rel_by_elem.end()) { + rel = rit->second; + const bool a_is_relating = A == ((ifcopenshell::IfcBaseEntity*)rel)->get_value("RelatingElement"); + a_type = ((ifcopenshell::IfcBaseEntity*)rel)->get_value("RelatingConnectionType"); + b_type = ((ifcopenshell::IfcBaseEntity*)rel)->get_value("RelatedConnectionType"); + if (!a_is_relating) { + std::swap(a_type, b_type); + } + } + +#if 0 + auto a_poly = ifcopenshell::geometry::utils::create_polyhedron(a.handle()->second); + auto b_poly = ifcopenshell::geometry::utils::create_polyhedron(b.handle()->second); + + std::wcout << "a" << std::endl; + for (auto& v : vertices(a_poly)) { + for (int i = 0; i < 3; ++i) { + std::wcout << CGAL::to_double(v->point().cartesian(i)) << " "; + } + std::wcout << std::endl; + } + + std::wcout << "b" << std::endl; + for (auto& v : vertices(b_poly)) { + for (int i = 0; i < 3; ++i) { + std::wcout << CGAL::to_double(v->point().cartesian(i)) << " "; + } + std::wcout << std::endl; + } +#endif + + std::ostringstream ss; + ss << A->data().to_string() << "x" << B->data().to_string() << std::endl; + std::clock_t intersection_begin = std::clock(); + auto x = a.handle()->second * b.handle()->second; + std::clock_t intersection_end = std::clock(); + + total_nef_intersection_time += (intersection_end - intersection_begin) / (double) CLOCKS_PER_SEC; + + if (x.is_empty()) { + return; + } + + std::clock_t poly_begin = std::clock(); + cgal_shape_t x_poly; + x.convert_to_polyhedron(x_poly); + std::clock_t poly_end = std::clock(); + conversion_to_poly += (poly_end - poly_begin) / (double)CLOCKS_PER_SEC; + + auto dza = a.bbox().zmax() - a.bbox().zmin(); + auto dzb = b.bbox().zmax() - b.bbox().zmin(); + auto bb = CGAL::Polygon_mesh_processing::bbox(x_poly); + if (bb.zmax() - bb.zmin() < std::min(dza, dzb) / 3.) { + return; + } + + CGAL::Polygon_mesh_processing::triangulate_faces(x_poly); + if (CGAL::Polygon_mesh_processing::area(x_poly) > 4.0) { + return; + } + + auto get_axis_parameter_min_max = [&c, &x_poly](const ifcopenshell::IfcBaseEntity* inst) { + auto item = c.mapping()->map(inst); + auto shaperep = taxonomy::cast(item)->children[0]; + auto loop = taxonomy::dcast(taxonomy::cast(shaperep)->children[0]); + + if (!loop) { + // std::wcout << "no suitable axis" << std::endl; + } else { + auto first_vertex = loop->children.front()->start; + auto last_vertex = loop->children.back()->end; + + if (first_vertex.which() != 0 || last_vertex.which() != 0) { + // std::wcout << "trims not supported" << std::endl; + } else { + auto p0 = boost::get(first_vertex); + auto p1 = boost::get(last_vertex); + + auto v0 = taxonomy::cast(item)->matrix->ccomponents() * p0->ccomponents().homogeneous(); + auto v1 = taxonomy::cast(item)->matrix->ccomponents() * p1->ccomponents().homogeneous(); + + auto P0 = Kernel_::Point_3(v0(0), v0(1), v0(2)); + auto P1 = Kernel_::Point_3(v1(0), v1(1), v1(2)); + + auto D = P1 - P0; + auto len = std::sqrt(CGAL::to_double(D.squared_length())); + D /= len; + + std::vector parameters; + + std::transform(vertices(x_poly).begin(), vertices(x_poly).end(), std::back_inserter(parameters), [&P0, D](cgal_vertex_descriptor_t& v) { + return (v->point() - P0) * D; + }); + + auto pit = std::minmax_element(parameters.begin(), parameters.end()); + return std::make_pair(len, std::make_pair(CGAL::to_double(*pit.first), CGAL::to_double(*pit.second))); + } + } + const auto& nan = std::numeric_limits::quiet_NaN(); + return std::make_pair(nan, std::make_pair(nan, nan)); + }; + + auto qualify_connection_type = [](double l, const std::pair& p) { + if (p.first < 1.e-3) { + return "ATSTART"; + } else if (p.second > l - 1.e-3) { + return "ATEND"; + } else { + return "ATPATH"; + } + }; + + auto alu0u1 = get_axis_parameter_min_max(A); + auto blu0u1 = get_axis_parameter_min_max(B); + + auto atype_computed = qualify_connection_type(alu0u1.first, alu0u1.second); + auto btype_computed = qualify_connection_type(blu0u1.first, blu0u1.second); + + rels_encounted.insert(rel); + + if (a_type != atype_computed || b_type != btype_computed) { + if (rel) { + logger::error(std::string("Connection type ") + atype_computed + " " + btype_computed + " for:", rel); + } else { + auto A_str = A->get_value("GlobalId"); + auto B_str = B->get_value("GlobalId"); + logger::error("No connection for adjacent " + A_str + " " + B_str); + } + } + }); + + std::for_each(rels->begin(), rels->end(), [&rels_encounted, &v](const ifcopenshell::IfcBaseClass* rel) { + if (rels_encounted.find(rel) == rels_encounted.end()) { + auto x = (ifcopenshell::IfcBaseEntity*)((ifcopenshell::IfcBaseEntity*)rel)->get_value("RelatingElement"); + auto y = (ifcopenshell::IfcBaseEntity*)((ifcopenshell::IfcBaseEntity*)rel)->get_value("RelatedElement"); + if (v.successfully_processed.find(x) != v.successfully_processed.end() && v.successfully_processed.find(y) != v.successfully_processed.end()) { + logger::error("Connection for non-adjacent walls", rel); + } + } + }); + + std::wcout << std::setprecision(14); + std::wcout << "total_map_time " << v.total_map_time << std::endl; + std::wcout << "total_geom_time " << v.total_geom_time << std::endl; + std::wcout << "total_nef_time " << v.total_nef_time << std::endl; + std::wcout << "total_minkowsky_time " << v.total_minkowsky_time << std::endl; + std::wcout << "total_box_time " << v.total_box_time << std::endl; + std::wcout << "total_nef_intersection_time " << total_nef_intersection_time << std::endl; + std::wcout << "total_conversion_to_poly_time " << conversion_to_poly << std::endl; +} + +#endif diff --git a/src/ifcconvert/validation_utils.cpp b/src/ifcconvert/validation_utils.cpp new file mode 100644 index 0000000000..e4e591f853 --- /dev/null +++ b/src/ifcconvert/validation_utils.cpp @@ -0,0 +1,32 @@ +#ifdef IFOPSH_WITH_CGAL + +#include "validation_utils.h" + +double facet_area(const cgal_shape_t::Facet_handle& f) { + auto p0 = f->facet_begin()->vertex()->point(); + auto p1 = f->facet_begin()->next()->vertex()->point(); + auto p2 = f->facet_begin()->next()->next()->vertex()->point(); + return std::sqrt(CGAL::to_double(CGAL::cross_product(p0 - p1, p2 - p1).squared_length())); +} + +void dump_facet(const cgal_shape_t::Facet_handle& f) { + auto p0 = f->facet_begin()->vertex()->point(); + auto p1 = f->facet_begin()->next()->vertex()->point(); + auto p2 = f->facet_begin()->next()->next()->vertex()->point(); + auto V = CGAL::cross_product(p0 - p1, p2 - p1); + auto d = std::sqrt(CGAL::to_double(V.squared_length())); + if (d > 1.e-20) { + V /= d; + } + + std::ostringstream oss; + oss.precision(8); + oss << "Facet with area " << facet_area(f) << " and normal (" + << CGAL::to_double(V.cartesian(0)) << " " << CGAL::to_double(V.cartesian(1)) << " " + << CGAL::to_double(V.cartesian(2)) << ")"; + + auto osss = oss.str(); + std::wcout << osss.c_str() << std::endl; +} + +#endif diff --git a/src/ifcconvert/validation_utils.h b/src/ifcconvert/validation_utils.h new file mode 100644 index 0000000000..47af806975 --- /dev/null +++ b/src/ifcconvert/validation_utils.h @@ -0,0 +1,602 @@ +#ifdef IFOPSH_WITH_CGAL + +#include "../ifcgeom/kernels/cgal/CgalKernel.h" +#include "../ifcgeom/IfcGeomFilter.h" +#include "../ifcgeom/Iterator.h" + +#include +#include + +#include +#if CGAL_VERSION_NR >= 1060000000 +#include +#else +#include +#endif +#include +#include + +#include +#include + +#if CGAL_VERSION_NR >= 1060000000 +#define variant_get std::get_if +#else +#define variant_get boost::get +#endif + +template +T enlarge(const T& t, double d = 1.e-5) { + typename T::NT min[3]; + typename T::NT max[3]; + for (int i = 0; i < t.dimension(); ++i) { + min[i] = t.min_coord(i) - d; + max[i] = t.max_coord(i) + d; + } + return T(min, max, t.handle()); +} + +template +struct Build_Offset : public CGAL::Modifier_base { + std::list input; + + void operator()(HDS& hds) { + // Postcondition: hds is a valid polyhedral surface. + CGAL::Polyhedron_incremental_builder_3 B(hds); + + int Nv = 0, Nf = 0; + for (auto& f : input) { + Nv += 3; + Nf += 1; + } + + B.begin_surface(Nv, Nf); + + for (auto& f : input) { + auto p0 = f->facet_begin()->vertex()->point(); + auto p1 = f->facet_begin()->next()->vertex()->point(); + auto p2 = f->facet_begin()->next()->next()->vertex()->point(); + + auto O = CGAL::centroid(p0, p1, p2); + + Kernel_::Point_3* p012[3] = { &p0, &p1, &p2 }; + for (int i = 0; i < 3; ++i) { + *p012[i] = CGAL::ORIGIN + (((*(p012[i])) - CGAL::ORIGIN) + ((*(p012[i])) - O)); + B.add_vertex(*p012[i]); + } + } + + Nv = 0; + for (int i = 0; i < Nf; ++i) { + B.begin_facet(); + B.add_vertex_to_facet(Nv++); + B.add_vertex_to_facet(Nv++); + B.add_vertex_to_facet(Nv++); + B.end_facet(); + } + + B.end_surface(); + } +}; + +template +std::list connected_faces(cgal_shape_t::Facet_handle f, const Ts& excluded) { + std::set fs = { f }; + + std::function process; + process = [&fs, &process, &excluded](cgal_shape_t::Facet_handle& f) { + cgal_shape_t::Halfedge_around_facet_circulator circ = f->facet_begin(), end(circ); + do { + auto ff = circ->opposite()->facet(); + if (excluded.find(ff) == excluded.end()) { + auto p = fs.insert(ff); + if (p.second) { + process(ff); + } + } + } while (++circ != end); + }; + + process(f); + return std::list(fs.begin(), fs.end()); +} + +template +struct Builder_With_Map : public CGAL::Modifier_base { + std::list input; + std::map mapping; + + void operator()(HDS& hds) { + // Postcondition: hds is a valid polyhedral surface. + CGAL::Polyhedron_incremental_builder_3 B(hds); + + std::set used_points; + + for (auto& f : input) { + cgal_shape_t::Halfedge_around_facet_circulator circ = f->facet_begin(), end(circ); + do { + auto P = circ->vertex()->point(); + auto it = mapping.find(P); + if (it == mapping.end()) { + std::wcout << "WARNING unprojected point :(" << std::endl; + } else { + P = it->second; + } + used_points.insert(P); + } while (++circ != end); + } + + B.begin_surface(used_points.size(), input.size()); + + for (auto& p : used_points) { + B.add_vertex(p); + } + + for (auto& f : input) { + B.begin_facet(); + cgal_shape_t::Halfedge_around_facet_circulator circ = f->facet_begin(), end(circ); + do { + auto P = circ->vertex()->point(); + auto it = mapping.find(P); + if (it == mapping.end()) { + std::wcout << "WARNING unprojected point :(" << std::endl; + } else { + P = it->second; + } + + auto jt = used_points.find(P); + if (jt == used_points.end()) { + throw std::runtime_error("Unable to map point"); + } + size_t idx = std::distance(used_points.begin(), jt); + std::wcout << "idx " << idx << std::endl; + B.add_vertex_to_facet(idx); + } while (++circ != end); + + B.end_facet(); + } + + B.end_surface(); + } +}; + +double facet_area(const cgal_shape_t::Facet_handle& f); + +void dump_facet(const cgal_shape_t::Facet_handle& f); + +struct remove_thickness { + typedef Kernel_::Point_3 Point; + typedef Kernel_::Plane_3 Plane; + typedef Kernel_::Vector_3 Vector; + typedef Kernel_::Segment_3 Segment; + typedef Kernel_::Ray_3 Ray; + + typedef CGAL::Polyhedron_3 Polyhedron; + typedef CGAL::AABB_face_graph_triangle_primitive Primitive; +#if CGAL_VERSION_NR >= 1060000000 + typedef CGAL::AABB_traits_3 AAbbTraits; +#else + typedef CGAL::AABB_traits AAbbTraits; +#endif + typedef CGAL::AABB_tree Tree; + typedef boost::optional::Type> Ray_intersection; + + cgal_shape_t polyhedron, polyhedron2, flattened; + + remove_thickness(const cgal_shape_t& p) + // edge_collapse(p) still does not work :( + : polyhedron(p) + , polyhedron2(p) { + CGAL::Polygon_mesh_processing::triangulate_faces(polyhedron); + CGAL::Polygon_mesh_processing::triangulate_faces(polyhedron2); + + std::list non_degenerate, degenerate, longitudinal; + std::set thin_sides; + + std::wcout << "ALL FACES:" << std::endl; + + for (auto& f : faces(polyhedron)) { + dump_facet(f); + if (facet_area(f) > 1.e-20) { + non_degenerate.push_back(f); + } else { + degenerate.push_front(f); + std::wcout << "Degenerate, area: " << facet_area(f) << std::endl; + } + } + + std::wcout << "NON DEGENERATE:" << std::endl; + for (auto& f : non_degenerate) { + dump_facet(f); + } + + cgal_shape_t enlarged_non_degenerate_triangles; + Build_Offset bo; + bo.input = non_degenerate; + enlarged_non_degenerate_triangles.delegate(bo); + + // @todo, first on non-enlarged faces, then on enlarged; to fix projection on concave surfaces where the enlarging operation shortens projection distances. + + Tree tree(faces(enlarged_non_degenerate_triangles).first, faces(enlarged_non_degenerate_triangles).second, enlarged_non_degenerate_triangles); + + std::map face_normals; + boost::associative_property_map> face_normals_map(face_normals); + CGAL::Polygon_mesh_processing::compute_face_normals(polyhedron, face_normals_map); + + for (auto& f : non_degenerate) { + auto O = CGAL::centroid( + f->facet_begin()->vertex()->point(), + f->facet_begin()->next()->vertex()->point(), + f->facet_begin()->next()->next()->vertex()->point() + ); + + Ray ray(O, -face_normals_map[f]); + + std::list intersections; + tree.all_intersections(ray, std::back_inserter(intersections)); + double N = std::numeric_limits::infinity(); + Point P; + for (auto& intersection : intersections) { + if (variant_get(&(intersection->first))) { + const Point* p = variant_get(&(intersection->first)); + const double d = std::sqrt(CGAL::to_double((*p - O).squared_length())); + if (d > 1.e-20 && d < N) { + N = d; + } + } + } + if (N != std::numeric_limits::infinity() && N > 1.e-4) { + thin_sides.insert(f); + } + } + + std::wcout << "THIN SIDES:" << std::endl; + for (auto& f : thin_sides) { + dump_facet(f); + } + + for (auto& f : non_degenerate) { + if (thin_sides.find(f) == thin_sides.end()) { + longitudinal.push_back(f); + } + } + + std::wcout << "LONGITUDONAL:" << std::endl; + for (auto& f : longitudinal) { + dump_facet(f); + } + + std::wcout << "faces " << faces(polyhedron).size() << "long " << longitudinal.size() << "thin " << thin_sides.size() << "non-degen " << non_degenerate.size() << std::endl; + + cgal_shape_t enlarged_indiv_triangles; + Build_Offset bo2; + bo2.input = longitudinal; + enlarged_indiv_triangles.delegate(bo2); + + { + std::ofstream ofs("enlarged.off"); + ofs.precision(17); + ofs << enlarged_indiv_triangles; + } + + Tree tree2(faces(enlarged_indiv_triangles).begin(), faces(enlarged_indiv_triangles).end(), enlarged_indiv_triangles); + + std::map new_points; + + for (Polyhedron::Facet_iterator fit = polyhedron.facets_begin(); + fit != polyhedron.facets_end(); + ++fit) { + if (CGAL::collinear( + fit->halfedge()->vertex()->point(), + fit->halfedge()->next()->vertex()->point(), + fit->halfedge()->opposite()->vertex()->point())) { + std::wcout << "degenerate triangle" << std::endl; + } + } + + for (auto& v : vertices(polyhedron)) { + auto O = v->point(); + + Kernel_::Vector_3 norm; + Kernel_::Vector_3 accum; + int count = 0; + CGAL::Face_around_target_circulator it(v->halfedge(), polyhedron), end(it); + do { + cgal_shape_t::Facet_handle fh = (*it)->halfedge()->facet(); + + auto jt = std::find(non_degenerate.begin(), non_degenerate.end(), fh); + std::wcout << "non degen: " << (jt != non_degenerate.end()) << std::endl; + auto kt = std::find(thin_sides.begin(), thin_sides.end(), fh); + std::wcout << "thin side: " << (kt != thin_sides.end()) << std::endl; + if (jt != non_degenerate.end() && kt == thin_sides.end()) { + // else degenerate, prevent div by zero, do not incorporate in vnorm. + // or else part of thin side + + auto p0 = (*it)->facet_begin()->vertex()->point(); + auto p1 = (*it)->facet_begin()->next()->vertex()->point(); + auto p2 = (*it)->facet_begin()->next()->next()->vertex()->point(); + + { + std::ostringstream oss; + oss.precision(8); + oss << "p0 " << p0.cartesian(0) << " " << p0.cartesian(1) << " " << p0.cartesian(2) << "\n"; + oss << "p1 " << p1.cartesian(0) << " " << p1.cartesian(1) << " " << p1.cartesian(2) << "\n"; + oss << "p2 " << p2.cartesian(0) << " " << p2.cartesian(1) << " " << p2.cartesian(2) << "\n"; + auto osss = oss.str(); + std::wcout << osss.c_str() << std::endl; + } + + auto fnorm = CGAL::cross_product(p0 - p1, p2 - p1); + fnorm /= std::sqrt(CGAL::to_double(fnorm.squared_length())); + + // const auto& fnorm = face_normals_map_2[*it]; + std::ostringstream oss; + oss.precision(8); + oss << fnorm.cartesian(0) << " " << fnorm.cartesian(1) << " " << fnorm.cartesian(2); + auto osss = oss.str(); + std::wcout << osss.c_str() << std::endl; + accum += fnorm; + + ++count; + } + + ++it; + } while (it != end); + + norm = accum / count; + std::wcout << "count " << count << std::endl; + + if (count == 0) { + // part of only degenerate or only thin sides + continue; + } + + // v->vertex_begin(); + Ray ray(O, norm); + std::ostringstream oss; + oss.precision(8); + oss << O << " -> " << norm; + auto osss = oss.str(); + std::wcout << osss.c_str() << std::endl; + + std::list intersections; + tree2.all_intersections(ray, std::back_inserter(intersections)); + double N = std::numeric_limits::infinity(); + Point P; + + bool used_intersection = false; + + if (intersections.size()) { + for (auto& intersection : intersections) { + if (variant_get(&(intersection->first))) { + const Point* p = variant_get(&(intersection->first)); + const double d = std::sqrt(CGAL::to_double((*p - O).squared_length())); + if (d < N && d > 1.e-20) { + N = d; + P = *p; + std::wcout << "intersection @ " << d << std::endl; + } + } + } + std::wcout << "-----------" << std::endl; + + // average the new point + new_points[O] = CGAL::ORIGIN + (((O - CGAL::ORIGIN) + (P - CGAL::ORIGIN))) / 2; + used_intersection = true; + } + + if (!used_intersection) { + std::wcout << "no intersection :(" << std::endl; + } + } + + auto thin_sides_degenerate = thin_sides; + thin_sides_degenerate.insert(degenerate.begin(), degenerate.end()); + + // @todo choose connected / connected_opposing based on largest combined area of facets? + + if (longitudinal.size() == 0) { + std::wcout << "no longitudinal faces detected :(" << std::endl; + return; + } + + auto connected = connected_faces(*longitudinal.begin(), thin_sides_degenerate); + decltype(connected) connected_opposing; + + for (auto& f : longitudinal) { + if (std::find(connected.begin(), connected.end(), f) == connected.end()) { + connected_opposing = connected_faces(f, thin_sides_degenerate); + + std::set longi(longitudinal.begin(), longitudinal.end()); + std::set both_sides(connected.begin(), connected.end()); + both_sides.insert(connected_opposing.begin(), connected_opposing.end()); + + if (longi == both_sides) { + std::wcout << "Facet connection functioning properly" << std::endl; + } else { + std::wcout << "Facet connection functioning incorrectly" << std::endl; + } + + break; + } + } + + Builder_With_Map b2; + b2.input = connected; + b2.mapping = new_points; + + flattened.delegate(b2); + } +}; + + +struct intersection_validator { + typedef std::list> > nefs_t; + typedef CGAL::Box_intersection_d::Box_with_handle_d Box; + + std::vector boxes; + nefs_t nefs; + + double total_map_time = 0.; + double total_geom_time = 0.; + double total_nef_time = 0.; + double total_minkowsky_time = 0.; + double total_box_time = 0.; + + std::set successfully_processed; + + intersection_validator(ifcopenshell::file& f, std::initializer_list entities, double eps, bool no_progress, bool quiet, bool stderr_progress) { + + ifcopenshell::geometry::Settings settings; + settings.get().value = false; + settings.get().value = false; + settings.get().value = true; + settings.get().value = true; + settings.get().value = ifcopenshell::geometry::settings::NATIVE; + settings.get().value = true; + + std::vector spaces_and_walls = { + IfcGeom::entity_filter(true, false, entities) + }; + + IfcGeom::Iterator context_iterator("cgal", settings, &f, spaces_and_walls, 1); + + if (!context_iterator.initialize()) { + return; + } + + auto polycube = ifcopenshell::geometry::utils::create_cube(eps); + auto cube = ifcopenshell::geometry::utils::create_nef_polyhedron(polycube); + + size_t num_created = 0; + int old_progress = quiet ? 0 : -1; + + for (;; ++num_created) { + bool has_more = true; + if (num_created) { + has_more = context_iterator.next(); + } + IfcGeom::BRepElement* geom_object = nullptr; + if (has_more) { + geom_object = context_iterator.get_native(); + } + if (!geom_object) { + break; + } + + std::stringstream ss; + geom_object->product()->to_string(ss); + auto sss = ss.str(); + std::wcout << sss.c_str() << std::endl; + + for (auto& g : geom_object->geometry()) { + cgal_shape_t s = *std::static_pointer_cast(g.Shape()); + const auto& m = g.Placement()->ccomponents(); + const auto& n = geom_object->transformation().data()->ccomponents(); + + const cgal_placement_t trsf( + m(0, 0), m(0, 1), m(0, 2), m(0, 3), + m(1, 0), m(1, 1), m(1, 2), m(1, 3), + m(2, 0), m(2, 1), m(2, 2), m(2, 3)); + + const cgal_placement_t trsf2( + n(0, 0), n(0, 1), n(0, 2), n(0, 3), + n(1, 0), n(1, 1), n(1, 2), n(1, 3), + n(2, 0), n(2, 1), n(2, 2), n(2, 3)); + + // Apply transformation + for (auto &vertex : vertices(s)) { + vertex->point() = vertex->point().transform(trsf).transform(trsf2); + } + + std::clock_t nef_begin = std::clock(); + CGAL::Nef_polyhedron_3 nef = ifcopenshell::geometry::utils::create_nef_polyhedron(s); + std::clock_t nef_end = std::clock(); + total_nef_time += (nef_end - nef_begin) / (double) CLOCKS_PER_SEC; + if (nef.is_empty()) { + std::wcout << "Failed to create nef" << std::endl; + continue; + } + + successfully_processed.insert(geom_object->product()); + + nef = CGAL::minkowski_sum_3(nef, cube); + std::clock_t minkowski_end = std::clock(); + total_minkowsky_time += (minkowski_end - nef_end) / (double) CLOCKS_PER_SEC; + + std::wcout << "product: " << geom_object->product() << std::endl; + nefs.push_back({ geom_object->product(), nef }); + + Box b(&*(nefs.rbegin())); + // id_map[b.id()] = ; + + for (auto &vertex : vertices(s)) { + double p[3] = { + CGAL::to_double(vertex->point().cartesian(0)), + CGAL::to_double(vertex->point().cartesian(1)), + CGAL::to_double(vertex->point().cartesian(2)) + }; + b.extend(p); + } + + boxes.push_back(enlarge(b)); + + /* + std::ostringstream ss; + ss << geom_object->product()->data().to_string() << std::endl << b.min_coord(0) << " - " << b.max_coord(0) << std::endl; + auto sss = ss.str(); + std::wcout << sss.c_str(); + */ + } + + if (!no_progress) { + if (quiet) { + const int progress = context_iterator.progress(); + for (; old_progress < progress; ++old_progress) { + std::cout << "."; + if (stderr_progress) + std::cerr << "."; + } + std::cout << std::flush; + if (stderr_progress) + std::cerr << std::flush; + } else { + const int progress = context_iterator.progress() / 2; + if (old_progress != progress) logger::progress_bar(progress); + old_progress = progress; + } + } + } + + if (!no_progress && quiet) { + for (; old_progress < 100; ++old_progress) { + std::cout << "."; + if (stderr_progress) + std::cerr << "."; + } + std::cout << std::flush; + if (stderr_progress) + std::cerr << std::flush; + } else { + logger::status("\rDone fixing space boundaries for " + boost::lexical_cast(num_created) + + " objects "); + } + + /* + // @todo + total_geom_time = context_iterator.converter().total_geom_time; + total_map_time = context_iterator.converter().total_map_time; + */ + } + + template + void operator()(Fn fn) { + std::clock_t box_overlap_begin = std::clock(); + CGAL::box_self_intersection_d(boxes.begin(), boxes.end(), [](Box& x, Box& y) {}); + std::clock_t box_overlap_end = std::clock(); + total_box_time += (box_overlap_end - box_overlap_begin) / (double) CLOCKS_PER_SEC; + CGAL::box_self_intersection_d(boxes.begin(), boxes.end(), fn); + } +}; + +#endif \ No newline at end of file diff --git a/src/serializers/CMakeLists.txt b/src/serializers/CMakeLists.txt index f875e68954..7e86046a9c 100644 --- a/src/serializers/CMakeLists.txt +++ b/src/serializers/CMakeLists.txt @@ -5,10 +5,10 @@ file(GLOB SERIALIZERS_H_FILES *.h) file(GLOB SERIALIZERS_S_H_FILES schema_dependent/*.h) function(add_document_serializer_plugin target output_name) - cmake_parse_arguments(PLUGIN "" "" "SOURCES;LIBRARIES" ${ARGN}) + cmake_parse_arguments(PLUGIN "" "" "SOURCES;LIBRARIES;DEFINITIONS" ${ARGN}) add_library(${target} SHARED ${PLUGIN_SOURCES}) - target_compile_definitions(${target} PRIVATE SERIALIZERS_EXPORTS BOOST_DLL_USE_STD_FS) + target_compile_definitions(${target} PRIVATE SERIALIZERS_EXPORTS BOOST_DLL_USE_STD_FS ${PLUGIN_DEFINITIONS}) target_link_libraries(${target} PRIVATE plugin IfcGeom IfcParse ${PLUGIN_LIBRARIES}) set_target_properties(${target} PROPERTIES OUTPUT_NAME "${output_name}" @@ -21,10 +21,10 @@ function(add_document_serializer_plugin target output_name) endfunction() function(add_geometry_serializer_plugin target output_name) - cmake_parse_arguments(PLUGIN "" "" "SOURCES;LIBRARIES" ${ARGN}) + cmake_parse_arguments(PLUGIN "" "" "SOURCES;LIBRARIES;DEFINITIONS" ${ARGN}) add_library(${target} SHARED ${PLUGIN_SOURCES}) - target_compile_definitions(${target} PRIVATE SERIALIZERS_EXPORTS BOOST_DLL_USE_STD_FS) + target_compile_definitions(${target} PRIVATE SERIALIZERS_EXPORTS BOOST_DLL_USE_STD_FS ${PLUGIN_DEFINITIONS}) target_link_libraries(${target} PRIVATE plugin IfcGeom IfcParse ${PLUGIN_LIBRARIES}) if(WITH_PROJ) find_package(proj REQUIRED) @@ -59,15 +59,15 @@ add_geometry_serializer_plugin(geometry_serializer_obj "geometry.obj" SOURCES ge add_geometry_serializer_plugin(geometry_serializer_ttl "geometry.ttl" SOURCES geometry_ttl_plugin.cpp TtlWktSerializer.cpp LIBRARIES ${ttl_geometry_serializer_libraries}) if(OPENCOLLADA_FOUND) - add_geometry_serializer_plugin(geometry_serializer_dae "geometry.dae" SOURCES geometry_dae_plugin.cpp ColladaSerializer.cpp LIBRARIES ${OPENCOLLADA_LIBRARIES}) + add_geometry_serializer_plugin(geometry_serializer_dae "geometry.dae" SOURCES geometry_dae_plugin.cpp ColladaSerializer.cpp LIBRARIES OpenCOLLADA::OpenCOLLADA DEFINITIONS WITH_OPENCOLLADA) endif() if(GLTF_SUPPORT) - add_geometry_serializer_plugin(geometry_serializer_glb "geometry.glb" SOURCES geometry_glb_plugin.cpp GltfSerializer.cpp) + add_geometry_serializer_plugin(geometry_serializer_glb "geometry.glb" SOURCES geometry_glb_plugin.cpp GltfSerializer.cpp LIBRARIES nlohmann_json::nlohmann_json DEFINITIONS WITH_GLTF) endif() if(USD_SUPPORT) - add_geometry_serializer_plugin(geometry_serializer_usd "geometry.usd" SOURCES geometry_usd_plugin.cpp USDSerializer.cpp LIBRARIES ${USD_LIBRARIES}) + add_geometry_serializer_plugin(geometry_serializer_usd "geometry.usd" SOURCES geometry_usd_plugin.cpp USDSerializer.cpp LIBRARIES pxr::USD) endif() if(WITH_OPENCASCADE) diff --git a/src/serializers/document_rdb_plugin.cpp b/src/serializers/document_rdb_plugin.cpp index 5d4b2e4a79..291e4c721f 100644 --- a/src/serializers/document_rdb_plugin.cpp +++ b/src/serializers/document_rdb_plugin.cpp @@ -51,6 +51,8 @@ plugin::metadata plugin_metadata() { void register_plugin(document_serializer_registry& registry, const plugin::module& module) { document_serializer_info info; info.format = "rdb"; + info.name = "RocksDB"; + info.description = "RocksDB key-value store serialization of IFC data."; info.supports_ifc_file = false; info.supports_input_filename = true; info.writes_final_output = true; diff --git a/src/serializers/document_serializer_plugin.cpp b/src/serializers/document_serializer_plugin.cpp index 8a00ae8944..2d15c7504f 100644 --- a/src/serializers/document_serializer_plugin.cpp +++ b/src/serializers/document_serializer_plugin.cpp @@ -65,6 +65,12 @@ void ifcopenshell::serializers::document_serializer_registry::bind(const documen entry entry; entry.info_ = info; entry.info_.format = document_serializer_key(entry.info_.format); + if (entry.info_.name.empty() && !entry.info_.format.empty()) { + entry.info_.name = boost::to_upper_copy(entry.info_.format); + } + if (entry.info_.description.empty()) { + entry.info_.description = entry.info_.name; + } entry.info_.schema_name = document_serializer_schema_key(entry.info_.schema_name); entry.create_ = create; entry.module_ = module.meta().id.empty() ? plugin::module(document_serializer_plugin_metadata(entry.info_.format, entry.info_.schema_name)) : module; diff --git a/src/serializers/document_serializer_plugin.h b/src/serializers/document_serializer_plugin.h index 47d7795772..5dfda5a707 100644 --- a/src/serializers/document_serializer_plugin.h +++ b/src/serializers/document_serializer_plugin.h @@ -38,6 +38,8 @@ namespace serializers { struct SERIALIZERS_API document_serializer_info { std::string format; + std::string name; + std::string description; std::string schema_name; bool supports_ifc_file = true; bool supports_input_filename = false; diff --git a/src/serializers/geometry_dae_plugin.cpp b/src/serializers/geometry_dae_plugin.cpp index 1bbc9d3061..31a288457d 100644 --- a/src/serializers/geometry_dae_plugin.cpp +++ b/src/serializers/geometry_dae_plugin.cpp @@ -44,6 +44,8 @@ boost::shared_ptr create_serializer(const geometry_serialize void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { geometry_serializer_info info; info.format = "dae"; + info.name = "Collada"; + info.description = "Digital Assets Exchange."; info.extensions = { ".dae" }; info.supports_triangulation = true; info.supports_user_element_hierarchy = true; diff --git a/src/serializers/geometry_glb_plugin.cpp b/src/serializers/geometry_glb_plugin.cpp index d7bfb17f09..d21f286b1b 100644 --- a/src/serializers/geometry_glb_plugin.cpp +++ b/src/serializers/geometry_glb_plugin.cpp @@ -44,6 +44,8 @@ boost::shared_ptr create_serializer(const geometry_serialize void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { geometry_serializer_info info; info.format = "glb"; + info.name = "glTF"; + info.description = "Binary glTF v2.0."; info.extensions = { ".glb" }; info.supports_triangulation = true; info.supports_user_element_hierarchy = true; diff --git a/src/serializers/geometry_hdf_plugin.cpp b/src/serializers/geometry_hdf_plugin.cpp index 62037be3c1..4ad6c8cecb 100644 --- a/src/serializers/geometry_hdf_plugin.cpp +++ b/src/serializers/geometry_hdf_plugin.cpp @@ -48,6 +48,8 @@ void configure_serializer(geometry_serializer_context& context) { void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { geometry_serializer_info info; info.format = "hdf"; + info.name = "HDF"; + info.description = "Hierarchical Data Format storing positions, normals and indices."; info.extensions = { ".h5" }; info.kernel_ids = { "opencascade" }; info.supports_triangulation = true; diff --git a/src/serializers/geometry_igs_plugin.cpp b/src/serializers/geometry_igs_plugin.cpp index b0b17f3752..e5e0344ae0 100644 --- a/src/serializers/geometry_igs_plugin.cpp +++ b/src/serializers/geometry_igs_plugin.cpp @@ -58,6 +58,8 @@ void configure_serializer(geometry_serializer_context& context) { void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { geometry_serializer_info info; info.format = "igs"; + info.name = "IGES"; + info.description = "Initial Graphics Exchange Specification."; info.extensions = { ".igs" }; info.kernel_ids = { "opencascade" }; info.supports_brep = true; diff --git a/src/serializers/geometry_obj_plugin.cpp b/src/serializers/geometry_obj_plugin.cpp index b9169a8e1b..cac598bb6c 100644 --- a/src/serializers/geometry_obj_plugin.cpp +++ b/src/serializers/geometry_obj_plugin.cpp @@ -60,6 +60,8 @@ void configure_serializer(geometry_serializer_context& context) { void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { geometry_serializer_info info; info.format = "obj"; + info.name = "WaveFront OBJ"; + info.description = "A .mtl file is also created."; info.extensions = { ".obj" }; info.supports_triangulation = true; registry.bind(info, create_serializer, configure_serializer, module); diff --git a/src/serializers/geometry_serializer_plugin.cpp b/src/serializers/geometry_serializer_plugin.cpp index 5693d9707c..e5ec3b1843 100644 --- a/src/serializers/geometry_serializer_plugin.cpp +++ b/src/serializers/geometry_serializer_plugin.cpp @@ -59,6 +59,12 @@ void ifcopenshell::serializers::geometry_serializer_registry::bind(const geometr entry entry; entry.info_ = info; entry.info_.format = boost::to_lower_copy(entry.info_.format); + if (entry.info_.name.empty() && !entry.info_.format.empty()) { + entry.info_.name = boost::to_upper_copy(entry.info_.format); + } + if (entry.info_.description.empty()) { + entry.info_.description = entry.info_.name; + } entry.create_ = create; entry.configure_ = configure; entry.module_ = module.meta().id.empty() ? plugin::module(geometry_serializer_plugin_metadata(entry.info_.format)) : module; diff --git a/src/serializers/geometry_serializer_plugin.h b/src/serializers/geometry_serializer_plugin.h index 7bb3a69324..9b5a31986a 100644 --- a/src/serializers/geometry_serializer_plugin.h +++ b/src/serializers/geometry_serializer_plugin.h @@ -37,6 +37,8 @@ namespace serializers { struct SERIALIZERS_API geometry_serializer_info { std::string format; + std::string name; + std::string description; std::vector extensions; std::vector kernel_ids; bool supports_triangulation = false; diff --git a/src/serializers/geometry_stp_plugin.cpp b/src/serializers/geometry_stp_plugin.cpp index 416de34e02..4c8818eb48 100644 --- a/src/serializers/geometry_stp_plugin.cpp +++ b/src/serializers/geometry_stp_plugin.cpp @@ -49,6 +49,8 @@ void configure_serializer(geometry_serializer_context& context) { void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { geometry_serializer_info info; info.format = "stp"; + info.name = "STEP"; + info.description = "Standard for the Exchange of Product Data."; info.extensions = { ".stp" }; info.kernel_ids = { "opencascade" }; info.supports_brep = true; diff --git a/src/serializers/geometry_svg_plugin.cpp b/src/serializers/geometry_svg_plugin.cpp index 0176811479..29ebf39e23 100644 --- a/src/serializers/geometry_svg_plugin.cpp +++ b/src/serializers/geometry_svg_plugin.cpp @@ -49,6 +49,8 @@ void configure_serializer(geometry_serializer_context& context) { void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { geometry_serializer_info info; info.format = "svg"; + info.name = "SVG"; + info.description = "Scalable Vector Graphics (2D floor plan)."; info.extensions = { ".svg" }; info.kernel_ids = { "opencascade" }; info.supports_brep = true; diff --git a/src/serializers/geometry_ttl_plugin.cpp b/src/serializers/geometry_ttl_plugin.cpp index ced5a3a6c5..f5cdf1f260 100644 --- a/src/serializers/geometry_ttl_plugin.cpp +++ b/src/serializers/geometry_ttl_plugin.cpp @@ -49,6 +49,8 @@ void configure_serializer(geometry_serializer_context& context) { void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { geometry_serializer_info info; info.format = "ttl"; + info.name = "TTL/WKT"; + info.description = "RDF Turtle with Well-Known Text geometry."; info.extensions = { ".ttl" }; info.supports_triangulation = true; info.supports_brep = true; diff --git a/src/serializers/geometry_usd_plugin.cpp b/src/serializers/geometry_usd_plugin.cpp index cba3abb9da..b1773e1ec5 100644 --- a/src/serializers/geometry_usd_plugin.cpp +++ b/src/serializers/geometry_usd_plugin.cpp @@ -44,6 +44,8 @@ boost::shared_ptr create_serializer(const geometry_serialize void register_plugin(geometry_serializer_registry& registry, const plugin::module& module) { geometry_serializer_info info; info.format = "usd"; + info.name = "USD"; + info.description = "Universal Scene Description."; info.extensions = { ".usd", ".usda", ".usdc" }; info.supports_triangulation = true; info.supports_user_element_hierarchy = true; diff --git a/src/serializers/schema_dependent/CMakeLists.txt b/src/serializers/schema_dependent/CMakeLists.txt index f65c1e9fdb..da508e41de 100644 --- a/src/serializers/schema_dependent/CMakeLists.txt +++ b/src/serializers/schema_dependent/CMakeLists.txt @@ -3,7 +3,7 @@ foreach(schema ${SCHEMA_VERSIONS}) target_compile_definitions(document_serializer_xml_ifc${schema} PRIVATE IfcSchema=Ifc${schema}) if(GLTF_SUPPORT) - add_document_serializer_plugin(document_serializer_json_ifc${schema} "document.json.ifc${schema}" SOURCES JsonSerializer.cpp json_plugin.cpp) + add_document_serializer_plugin(document_serializer_json_ifc${schema} "document.json.ifc${schema}" SOURCES JsonSerializer.cpp json_plugin.cpp LIBRARIES nlohmann_json::nlohmann_json DEFINITIONS WITH_GLTF) target_compile_definitions(document_serializer_json_ifc${schema} PRIVATE IfcSchema=Ifc${schema}) endif() endforeach() diff --git a/src/serializers/schema_dependent/json_plugin.cpp b/src/serializers/schema_dependent/json_plugin.cpp index f4742d732e..e76522f6e9 100644 --- a/src/serializers/schema_dependent/json_plugin.cpp +++ b/src/serializers/schema_dependent/json_plugin.cpp @@ -53,6 +53,8 @@ plugin::metadata plugin_metadata() { void register_plugin(document_serializer_registry& registry, const plugin::module& module) { document_serializer_info info; info.format = "json"; + info.name = "JSON"; + info.description = "Property definitions and decomposition tree in xeokit JSON format."; info.schema_name = STRINGIFY(IfcSchema); registry.bind(info, create_serializer, module); } diff --git a/src/serializers/schema_dependent/xml_plugin.cpp b/src/serializers/schema_dependent/xml_plugin.cpp index d4509f71d9..5b2d495c6a 100644 --- a/src/serializers/schema_dependent/xml_plugin.cpp +++ b/src/serializers/schema_dependent/xml_plugin.cpp @@ -48,6 +48,8 @@ plugin::metadata plugin_metadata() { void register_plugin(document_serializer_registry& registry, const plugin::module& module) { document_serializer_info info; info.format = "xml"; + info.name = "XML"; + info.description = "Property definitions and decomposition tree."; info.schema_name = STRINGIFY(IfcSchema); registry.bind(info, create_serializer, module); }