diff --git a/src/ifcgeom/Converter.cpp b/src/ifcgeom/Converter.cpp index 5d454f8809..793778e508 100644 --- a/src/ifcgeom/Converter.cpp +++ b/src/ifcgeom/Converter.cpp @@ -177,7 +177,15 @@ IfcGeom::BRepElement* ifcopenshell::geometry::Converter::create_brep_for_represe // Note that openings for IfcOpeningElements are not processed auto openings = mapping_->find_openings(product); - if (!settings_.get().get() && !openings.empty()) { + const bool no_openings = openings.empty(); + const bool disable_opening_subtractions = settings_.get().get(); + const bool above_limit = settings_.get().has() && settings_.get().get() != 0 && openings.size() > settings_.get().get(); + + if (above_limit) { + logger_.warning("GEO", 403, "Element has more openings than the maximum allowed. Openings will not be processed for this element:", product); + } + + if (!no_openings && !disable_opening_subtractions && !above_limit) { representation_id_builder << "-openings"; for (auto& op : openings) { representation_id_builder << "-" << op.id(); diff --git a/src/ifcgeom/tests/CMakeLists.txt b/src/ifcgeom/tests/CMakeLists.txt index 52307850e2..4bc6ec6e17 100644 --- a/src/ifcgeom/tests/CMakeLists.txt +++ b/src/ifcgeom/tests/CMakeLists.txt @@ -3,9 +3,20 @@ add_executable(test_ifcopenshell_geometry test_ifcopenshell_geometry.cpp ) -target_link_libraries(test_ifcopenshell_geometry PRIVATE Catch2::Catch2WithMain) +if(NOT TARGET parse_schema_ifc2x3) + message(FATAL_ERROR "test_ifcopenshell_geometry requires SCHEMA_VERSIONS to include 2x3.") +endif() +target_include_directories(test_ifcopenshell_geometry PRIVATE "${CMAKE_SOURCE_DIR}/../src") +target_link_libraries(test_ifcopenshell_geometry PRIVATE Catch2::Catch2WithMain IfcGeom IfcParse parse_schema_ifc2x3) +add_dependencies(test_ifcopenshell_geometry geometry_mapping_ifc2x3 geometry_kernel_passthrough) catch_discover_tests(test_ifcopenshell_geometry DL_PATHS $ $ + $ + $ + $ + $ + # Catch2 discovery uses DL_PATHS in order; CTest PATH prepends reverse it. + $ ) diff --git a/src/ifcgeom/tests/test_ifcopenshell_geometry.cpp b/src/ifcgeom/tests/test_ifcopenshell_geometry.cpp index 36fa0aa699..2bee8548e2 100644 --- a/src/ifcgeom/tests/test_ifcopenshell_geometry.cpp +++ b/src/ifcgeom/tests/test_ifcopenshell_geometry.cpp @@ -1,7 +1,81 @@ -// This file was generated with the assistance of an AI coding tool. +#include +#include #include -TEST_CASE("IfcGeom C++ test scaffold is registered", "[ifcgeom]") { - REQUIRE(true); +#include "ifcgeom/Converter.h" +#include "ifcgeom/kernel_registry.h" +#define IfcSchema Ifc2x3 +#include "ifcparse/hierarchy_helper.h" +#include "ifcparse/macros.h" +#include "ifcparse/schemas/Ifc2x3.h" + +namespace { + +constexpr int MAX_VOIDS = 30; + +IfcSchema::IfcWallStandardCase create_wall_with_voids(hierarchy_helper& file, int void_count) { + file.header().file_name().setname("wall-with-voids.ifc"); + + auto wall = file.create(); + wall.setGlobalId(ifcopenshell::global_id()); + wall.setName("Wall with " + std::to_string(void_count) + " voids"); + file.addBuildingProduct(wall); + + const auto owner_history = file.getSingle(); + const auto storey_placement = file.getSingle().ObjectPlacement(); + const double opening_spacing = 350.0; + const double wall_width = std::max(12000.0, void_count * opening_spacing + 1000.0); + const double wall_depth = 360.0; + const double wall_height = 3000.0; + + wall.setOwnerHistory(owner_history); + wall.setObjectPlacement(file.addLocalPlacement(storey_placement)); + wall.setRepresentation(file.addAxisBox(wall_width, wall_depth, wall_height)); + + const double first_opening_x = -((void_count - 1) * opening_spacing) / 2.0; + for (int i = 0; i < void_count; ++i) { + auto opening = file.create(); + opening.setGlobalId(ifcopenshell::global_id()); + opening.setOwnerHistory(owner_history); + opening.setName("Opening " + std::to_string(i + 1)); + opening.setObjectPlacement(file.addLocalPlacement( + wall.ObjectPlacement(), + first_opening_x + i * opening_spacing, + 0.0, + 900.0)); + opening.setRepresentation(file.addBox(200.0, wall_depth + 40.0, 1200.0)); + + auto void_element = file.create(); + void_element.setGlobalId(ifcopenshell::global_id()); + void_element.setOwnerHistory(owner_history); + void_element.setRelatingBuildingElement(wall); + void_element.setRelatedOpeningElement(opening); + } + + return wall; +} + +std::size_t count_geo403_for_wall(hierarchy_helper& file, const IfcSchema::IfcWallStandardCase& wall) { + ifcopenshell::geometry::Settings settings; + settings.set("max-voids-per-element", MAX_VOIDS); + + logger log; + log.output_format(logger::FMT_INMEMORY); + ifcopenshell::geometry::Converter converter( + ifcopenshell::geometry::kernels::construct(&file, "opencascade", settings), &file, settings, log); + delete converter.create_brep_for_representation_and_product(wall.Representation().Representations().back(), wall); + return log.count("GEO403"); +} + +} // namespace + +TEST_CASE("IfcGeom C++ fixture creates walls below and above the void limit", "[ifcgeom][voids]") { + hierarchy_helper below_limit_file; + const auto below_limit_wall = create_wall_with_voids(below_limit_file, MAX_VOIDS - 1); + REQUIRE(count_geo403_for_wall(below_limit_file, below_limit_wall) == 0); + + hierarchy_helper above_limit_file; + const auto above_limit_wall = create_wall_with_voids(above_limit_file, MAX_VOIDS + 1); + REQUIRE(count_geo403_for_wall(above_limit_file, above_limit_wall) == 1); } diff --git a/src/ifcparse/logger.cpp b/src/ifcparse/logger.cpp index 6655d29eff..09bb1ad8b9 100644 --- a/src/ifcparse/logger.cpp +++ b/src/ifcparse/logger.cpp @@ -290,6 +290,15 @@ std::string logger::get_log() { return log_stream_.str(); } +std::size_t logger::count(const std::string& code) { + std::lock_guard lock(mutex_); + std::size_t count = 0; + for (const auto& message : log_messages_) { + count += code == message.code; + } + return count; +} + void logger::clear() { std::lock_guard lock(mutex_); log_stream_.str(std::string()); diff --git a/src/ifcparse/logger.h b/src/ifcparse/logger.h index 3e4f05b7f8..9f41b00574 100644 --- a/src/ifcparse/logger.h +++ b/src/ifcparse/logger.h @@ -24,6 +24,7 @@ #include "express.h" #include +#include #include #include #include @@ -136,6 +137,7 @@ class IFC_PARSE_API logger { void progress_bar(int progress); std::string get_log(); + std::size_t count(const std::string& code); void clear(); void append(logger& other); void print_performance_stats();