From 57c3d4b17674feaf83c4298badf46df124517e42 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sun, 19 Jul 2026 23:41:24 +0300 Subject: [PATCH] IfcConvert: remove unmaintained WITH_RELATIONSHIP_VALIDATION feature The validate_space_boundaries, validate_storey_containment and validate_wall_connectivity translation units only ever built when the default-off WITH_RELATIONSHIP_VALIDATION CMake option was enabled, and their fix_* entry points were never reachable from the IfcConvert command line. The code has drifted from the current geometry kernel API (Iterator now takes a std::unique_ptr, create_nef_polyhedron takes a CGAL polyhedron rather than a CgalShape), so any build with the option enabled fails to compile, as reported in the issue for MSVC and reproducible with clang on macOS. Per maintainer direction on the issue, remove the feature instead of patching it: the plan is to redo this kind of validation in Python on top of ConversionResult(Shape). This deletes the validation sources, the option, and the conditional source glob, leaving IfcConvert built from IfcConvert.cpp alone, which was already the default. Fixes #7444. Generated with the assistance of an AI coding tool. --- cmake/CMakeLists.txt | 1 - src/ifcconvert/CMakeLists.txt | 10 +- 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 | 603 ------------------ 7 files changed, 1 insertion(+), 1270 deletions(-) delete mode 100644 src/ifcconvert/validate_space_boundaries.cpp delete mode 100644 src/ifcconvert/validate_storey_containment.cpp delete mode 100644 src/ifcconvert/validate_wall_connectivity.cpp delete mode 100644 src/ifcconvert/validation_utils.cpp delete mode 100644 src/ifcconvert/validation_utils.h diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 9a3807d837..89b6cbbbe2 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -104,7 +104,6 @@ option(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON) option(WITH_PROJ "Enable output of Earth-Centered Earth-Fixed glTF output using the PROJ library" OFF) option(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON) option(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF) -option(WITH_RELATIONSHIP_VALIDATION "Build IfcConvert with option to validate geometrical relationships." OFF) option(WITH_ROCKSDB "Support a RocksDB key-value store as a file backend in IfcOpenShell" OFF) option(WITH_ZSTD "Use Zstd compression in RocksDB writes" OFF) diff --git a/src/ifcconvert/CMakeLists.txt b/src/ifcconvert/CMakeLists.txt index af46bc7dfe..5e378ba2a1 100644 --- a/src/ifcconvert/CMakeLists.txt +++ b/src/ifcconvert/CMakeLists.txt @@ -1,13 +1,5 @@ # IfcConvert -if(WITH_RELATIONSHIP_VALIDATION) - file(GLOB IFCCONVERT_CPP_FILES *.cpp) - file(GLOB IFCCONVERT_H_FILES *.h) -else() - file(GLOB IFCCONVERT_CPP_FILES IfcConvert.cpp) - file(GLOB IFCCONVERT_H_FILES) -endif() -set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES}) -add_executable(IfcConvert ${IFCCONVERT_FILES}) +add_executable(IfcConvert IfcConvert.cpp) target_link_libraries( IfcConvert diff --git a/src/ifcconvert/validate_space_boundaries.cpp b/src/ifcconvert/validate_space_boundaries.cpp deleted file mode 100644 index 6572d1d659..0000000000 --- a/src/ifcconvert/validate_space_boundaries.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#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(IfcParse::IfcFile& f, bool no_progress, bool quiet, bool stderr_progress, Logger& logger = Logger::Root()) { - intersection_validator v(f, { "IfcWall", "IfcSpace", "IfcSlab", "IfcCovering" }, 1.e-5, no_progress, quiet, stderr_progress, logger); - - auto rels = f.instances_by_type("IfcRelSpaceBoundary"); - - std::map, const IfcUtil::IfcBaseClass*> rel_by_space_elem; - - - if (rels) { - std::for_each(rels->begin(), rels->end(), [&rel_by_space_elem](const IfcUtil::IfcBaseClass* rel) { - auto x = ((IfcUtil::IfcBaseEntity*)rel)->get_value("RelatingSpace"); - try { - auto y = ((IfcUtil::IfcBaseEntity*)rel)->get_value("RelatedBuildingElement"); - rel_by_space_elem.insert({ { x,y }, rel }); - } catch (IfcParse::IfcException&) { - // RelatedBuildingElement can be NULL - } - }); - } - - std::set rels_encounted; - - IfcParse::IfcFile 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(ifcopenshell::geometry::kernels::construct(&f2, "cgal", settings, logger), &f2, settings, logger); - - std::map, std::vector> elem_to_space_boundary_coords; - - for (auto& i : *f2.instances_by_type("IfcProduct")) { - auto n = ((IfcUtil::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([&logger, &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().toString() << "x" << id_map[b.id()]->first->data().toString() << 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().toString() << "x" << a.handle()->first->data().toString() << 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("VAL", 1, "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("VAL", 2, "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 = ((IfcUtil::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("VAL", 3, "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 deleted file mode 100644 index 7c40cce60a..0000000000 --- a/src/ifcconvert/validate_storey_containment.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#ifdef IFOPSH_WITH_CGAL - -#include "../ifcgeom/kernels/cgal/CgalKernel.h" -#include "../ifcgeom/IfcGeomFilter.h" -#include "../ifcgeom/Iterator.h" - -#include -#include - -#include - -void fix_storeycontainment(IfcParse::IfcFile& f, bool no_progress, bool quiet, bool stderr_progress, Logger& logger = Logger::Root()) { - 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(ifcopenshell::geometry::kernels::construct(&f, "cgal", settings, logger), settings, &f, no_openings_and_spaces, 1, logger); - - auto get_elevation = [](const IfcUtil::IfcBaseClass* a) { - return ((const IfcUtil::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](IfcUtil::IfcBaseClass* r) { - auto elems = ((IfcUtil::IfcBaseEntity*)r)->get_value("RelatedElements"); - auto storey = ((IfcUtil::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 IfcUtil::IfcBaseClass* a, const IfcUtil::IfcBaseClass* b) { - return get_elevation(a) < get_elevation(b); - }); - - /* - std::wcout << "Storeys "; - for (auto& s : storeys_sorted) { - auto n = ((IfcUtil::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().toString(); - 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 = ((IfcUtil::IfcBaseEntity*)storeys_sorted[calc_idx])->get_value("GlobalId"); - auto s2 = ((IfcUtil::IfcBaseEntity*)elem_to_storey[geom_object->product()])->get_value("GlobalId"); - logger.Error("VAL", 4, "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.ProgressBar(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 deleted file mode 100644 index 28e8849749..0000000000 --- a/src/ifcconvert/validate_wall_connectivity.cpp +++ /dev/null @@ -1,201 +0,0 @@ -#ifdef IFOPSH_WITH_CGAL - -#include "validation_utils.h" - -#include -#include - -#include - -using namespace ifcopenshell::geometry; - -void fix_wallconnectivity(IfcParse::IfcFile& f, bool no_progress, bool quiet, bool stderr_progress, Logger& logger = Logger::Root()) { - intersection_validator v(f, { "IfcWall" }, 1.e-3, no_progress, quiet, stderr_progress, logger); - - 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(ifcopenshell::geometry::kernels::construct(&f, "cgal", settings, logger), &f, settings, logger); - - auto rels = f.instances_by_type("IfcRelConnectsPathElements"); - std::map, const IfcUtil::IfcBaseClass*> rel_by_elem; - std::for_each(rels->begin(), rels->end(), [&rel_by_elem](const IfcUtil::IfcBaseClass* rel) { - auto x = ((IfcUtil::IfcBaseEntity*)rel)->get_value("RelatingElement"); - auto y = ((IfcUtil::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([&logger, &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 IfcUtil::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 == ((IfcUtil::IfcBaseEntity*)rel)->get_value("RelatingElement"); - a_type = ((IfcUtil::IfcBaseEntity*)rel)->get_value("RelatingConnectionType"); - b_type = ((IfcUtil::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().toString() << "x" << B->data().toString() << 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 IfcUtil::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("VAL", 5, 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("VAL", 6, "No connection for adjacent " + A_str + " " + B_str); - } - } - }); - - std::for_each(rels->begin(), rels->end(), [&logger, &rels_encounted, &v](const IfcUtil::IfcBaseClass* rel) { - if (rels_encounted.find(rel) == rels_encounted.end()) { - auto x = (IfcUtil::IfcBaseEntity*)((IfcUtil::IfcBaseEntity*)rel)->get_value("RelatingElement"); - auto y = (IfcUtil::IfcBaseEntity*)((IfcUtil::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("VAL", 7, "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 deleted file mode 100644 index e4e591f853..0000000000 --- a/src/ifcconvert/validation_utils.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#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 deleted file mode 100644 index 5a98c07326..0000000000 --- a/src/ifcconvert/validation_utils.h +++ /dev/null @@ -1,603 +0,0 @@ -#ifdef IFOPSH_WITH_CGAL - -#include "../ifcgeom/kernels/cgal/CgalKernel.h" -#include "../ifcgeom/IfcGeomFilter.h" -#include "../ifcgeom/Iterator.h" -#include "../ifcgeom/hybrid_kernel.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(IfcParse::IfcFile& f, std::initializer_list entities, double eps, bool no_progress, bool quiet, bool stderr_progress, Logger& logger = Logger::Root()) { - - 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(ifcopenshell::geometry::kernels::construct(&f, "cgal", settings, logger), settings, &f, spaces_and_walls, 1, logger); - - 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()->toString(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().toString() << 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.ProgressBar(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