From 8f0d053049262a508821f33a9aa1a46a588a4cd4 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Thu, 19 Jun 2025 14:02:58 +0200 Subject: [PATCH] Factor out code into Iterator.cpp --- src/ifcgeom/Iterator.cpp | 652 +++++++++++++++++++++++++++++++++ src/ifcgeom/Iterator.h | 769 ++++----------------------------------- 2 files changed, 722 insertions(+), 699 deletions(-) create mode 100644 src/ifcgeom/Iterator.cpp diff --git a/src/ifcgeom/Iterator.cpp b/src/ifcgeom/Iterator.cpp new file mode 100644 index 0000000000..0e82e2a678 --- /dev/null +++ b/src/ifcgeom/Iterator.cpp @@ -0,0 +1,652 @@ +#include "Iterator.h" + +/** +* @return Returns true if the iterator is initialized with any elements, false otherwise. +* +* @note +* - A true return value does not guarantee successful initialization of all elements. +* Some elements may have failed to initialize. Check had_error_processing_elements() +* to see whether there were errors during the initialization. +* +* - For non-concurrent iterators, a false return may occur if initialization of the first +* element fails, even if subsequent elements could be initialized successfully. +*/ +bool IfcGeom::Iterator::initialize() { + using std::chrono::high_resolution_clock; + + if (initialization_outcome_) { + return *initialization_outcome_; + } + + time_points[0] = high_resolution_clock::now(); + std::vector reps; + if (num_threads_ != 1) { + // @todo this shouldn't be necessary with properly immutable taxonomy items + converter_->mapping()->use_caching() = false; + } + try { + converter_->mapping()->get_representations(reps, filters_); + } catch (const std::exception& e) { + Logger::Error(e); + } + time_points[1] = high_resolution_clock::now(); + + for (auto& task : reps) { + geometry_conversion_result res; + res.index = task.index; + if (!settings_.get().get()) { + res.representation = task.representation; + res.products_2 = task.products; + } else { + res.item = converter_->mapping()->map(task.representation); + if (!res.item) { + continue; + } + std::transform(task.products->begin(), task.products->end(), std::back_inserter(res.products), [this, &res](IfcUtil::IfcBaseClass* prod) { + auto prod_item = converter_->mapping()->map(prod); + return std::make_pair(prod->as(), ifcopenshell::geometry::taxonomy::cast(prod_item)->matrix); + }); + } + tasks_.push_back(res); + } + + if (settings_.get().get() && settings_.get().get()) { + std::unordered_map< + ifcopenshell::geometry::taxonomy::item::ptr, + std::vector>> folded; + + for (auto& r : tasks_) { + auto i = r.item; + + Eigen::Matrix4d m4 = Eigen::Matrix4d::Identity(); + + while (auto col = std::dynamic_pointer_cast(i)) { + if (col->children.size() == 1) { + if (col->matrix) { + m4 *= col->matrix->ccomponents(); + } + i = col->children[0]; + } else { + break; + } + } + + for (auto& p : r.products) { + auto pl = ifcopenshell::geometry::taxonomy::matrix4::ptr(p.second->clone_()); + pl->components() *= m4; + folded[i].push_back( + { p.first, pl } + ); + } + } + + if (folded.size() < tasks_.size()) { + auto old_size = tasks_.size(); + tasks_.clear(); + size_t i = 0; + for (auto& p : folded) { + tasks_.emplace_back(); + tasks_.back().index = i++; + tasks_.back().item = p.first; + tasks_.back().products = p.second; + } + Logger::Notice("Merged " + std::to_string(old_size) + " tasks into " + std::to_string(tasks_.size()) + " tasks due to permissive shape reuse"); + } + } + + size_t num_products = 0; + for (auto& r : tasks_) { + num_products += !settings_.get().get() ? r.products_2->size() : r.products.size(); + } + + time_points[2] = high_resolution_clock::now(); + + /* + // What to do, map representation and product individually? + // There needs to be two options, mapped item respecting (does that still work?), and optimized based on topology sorting. + // Or is the sorting not necessary if we just cache? + + std::vector items; + std::map placements; + std::transform(products.begin(), products.end(), std::back_inserter(items), [this, &placements](IfcUtil::IfcBaseClass* p) { + auto item = converter_->mapping()->map(p); + // Product placements do not affect item reuse and should temporarily be swapped to identity + if (item) { + std::swap(placements[item], ((taxonomy::geom_ptr)item)->matrix); + } + return item; + }); + items.erase(std::remove(items.begin(), items.end(), nullptr), items.end()); + std::sort(items.begin(), items.end(), taxonomy::less); + auto it = items.begin(); + while (it < items.end()) { + auto jt = std::upper_bound(it, items.end(), *it, taxonomy::less); + geometry_conversion_result r; + r.item = *it; + std::transform(it, jt, std::back_inserter(r.products), [&r, &placements](taxonomy::ptr product_node) { + return std::make_pair((IfcUtil::IfcBaseEntity*) product_node->instance, placements[product_node]); + }); + tasks_.push_back(r); + it = jt; + } + */ + + Logger::Notice("Created " + boost::lexical_cast(tasks_.size()) + " tasks for " + boost::lexical_cast(num_products) + " products"); + + if (tasks_.size() == 0) { + Logger::Warning("No representations encountered, aborting"); + initialization_outcome_.reset(false); + } else { + + task_iterator_ = tasks_.begin(); + + task_result_index_ = 0; + done = 0; + total = (int)tasks_.size(); + + if (num_threads_ != 1) { + init_future_ = std::async(std::launch::async, [this]() { process_concurrently(); }); + + // wait for the first element, because after init(), get() can be called. + // so the element conversion must succeed + initialization_outcome_ = wait_for_element(); + } else { + initialization_outcome_ = create(); + } + } + + return *initialization_outcome_; +} + +void IfcGeom::Iterator::process_finished_rep(geometry_conversion_result* rep) { + if (rep->elements.empty()) { + return; + } + + std::lock_guard lk(element_ready_mutex_); + + all_processed_elements_.insert(all_processed_elements_.end(), rep->elements.begin(), rep->elements.end()); + all_processed_native_elements_.insert(all_processed_native_elements_.end(), rep->breps.begin(), rep->breps.end()); + + if (!task_result_ptr_initialized) { + task_result_iterator_ = all_processed_elements_.begin(); + native_task_result_iterator_ = all_processed_native_elements_.begin(); + task_result_ptr_initialized = true; + } + + progress_ = (int)(++processed_ * 100 / tasks_.size()); +} + +void IfcGeom::Iterator::process_concurrently() { + size_t conc_threads = num_threads_; + if (conc_threads > tasks_.size()) { + conc_threads = tasks_.size(); + } + + kernel_pool.reserve(conc_threads); + for (unsigned i = 0; i < conc_threads; ++i) { + kernel_pool.push_back(new ifcopenshell::geometry::Converter(geometry_library_, ifc_file, settings_)); + } + + std::vector> threadpool; + + for (auto& rep : tasks_) { + ifcopenshell::geometry::Converter* K = nullptr; + if (threadpool.size() < kernel_pool.size()) { + K = kernel_pool[threadpool.size()]; + } + + while (threadpool.size() == conc_threads) { + for (int i = 0; i < (int)threadpool.size(); i++) { + auto& fu = threadpool[i]; + std::future_status status; + status = fu.wait_for(std::chrono::seconds(0)); + if (status == std::future_status::ready) { + process_finished_rep(fu.get()); + + std::swap(threadpool[i], threadpool.back()); + threadpool.pop_back(); + std::swap(kernel_pool[i], kernel_pool.back()); + K = kernel_pool.back(); + break; + } // if + } // for + } // while + + std::future fu = std::async( + std::launch::async, [this]( + ifcopenshell::geometry::Converter* kernel, + ifcopenshell::geometry::Settings settings, + geometry_conversion_result* rep) { + // Catch exceptions to be safe from freezing the iterator. + try { + this->create_element_(kernel, settings, rep); + } catch (const std::exception& e) { + Logger::Error( + std::string("Exception '") + e.what() + + std::string("' occurred while iterator was creating a shape: "), + rep->item->instance + ); + had_error_processing_elements_ = true; + } catch (...) { + Logger::Error( + "Unknown exception occurred while iteartor was creating a shape: ", + rep->item->instance + ); + had_error_processing_elements_ = true; + } + return rep; + }, + K, + std::ref(settings_), + &rep); + + if (terminating_) { + break; + } + + threadpool.emplace_back(std::move(fu)); + } + + for (auto& fu : threadpool) { + process_finished_rep(fu.get()); + } + + finished_ = true; + + Logger::SetProduct(boost::none); + + if (!terminating_) { + Logger::Status("\rDone creating geometry (" + boost::lexical_cast(all_processed_elements_.size()) + + " objects) "); + } +} + +/// Computes model's bounding box (bounds_min and bounds_max). +/// @note Can take several minutes for large files. +void IfcGeom::Iterator::compute_bounds(bool with_geometry) +{ + for (int i = 0; i < 3; ++i) { + bounds_min_.components()(i) = std::numeric_limits::infinity(); + bounds_max_.components()(i) = -std::numeric_limits::infinity(); + } + + if (with_geometry) { + size_t num_created = 0; + do { + IfcGeom::Element* geom_object = get(); + const IfcGeom::TriangulationElement* o = static_cast(geom_object); + const IfcGeom::Representation::Triangulation& mesh = o->geometry(); + auto mat = o->transformation().data()->ccomponents(); + Eigen::Vector4d vec, transformed; + + for (typename std::vector::const_iterator it = mesh.verts().begin(); it != mesh.verts().end();) { + const double& x = *(it++); + const double& y = *(it++); + const double& z = *(it++); + vec << x, y, z, 1.; + transformed = mat * vec; + + for (int i = 0; i < 3; ++i) { + bounds_min_.components()(i) = std::min(bounds_min_.components()(i), transformed(i)); + bounds_max_.components()(i) = std::max(bounds_max_.components()(i), transformed(i)); + } + } + } while (++num_created, next()); + } else { + std::vector reps; + converter_->mapping()->get_representations(reps, filters_); + + std::vector products; + for (auto& r : reps) { + std::copy(r.products->begin(), r.products->end(), std::back_inserter(products)); + } + + for (auto& product : products) { + auto prod_item = converter_->mapping()->map(product); + auto vec = ifcopenshell::geometry::taxonomy::cast(prod_item)->matrix->translation_part(); + + for (int i = 0; i < 3; ++i) { + bounds_min_.components()(i) = std::min(bounds_min_.components()(i), vec(i)); + bounds_max_.components()(i) = std::max(bounds_max_.components()(i), vec(i)); + } + } + } +} + +const IfcUtil::IfcBaseClass* IfcGeom::Iterator::create_shape_model_for_next_entity() { + geometry_conversion_result* task = nullptr; + for (; task_iterator_ < tasks_.end();) { + task = &*task_iterator_++; + create_element_(converter_, settings_, task); + if (task->elements.empty()) { + task = nullptr; + } else { + break; + } + } + if (task) { + process_finished_rep(task); + return task->item->instance->as(); + } else { + return nullptr; + } +} + +void IfcGeom::Iterator::create_element_(ifcopenshell::geometry::Converter* kernel, ifcopenshell::geometry::Settings settings, geometry_conversion_result* rep) +{ + if (!settings_.get().get()) { + rep->item = kernel->mapping()->map(rep->representation); + if (!rep->item) { + return; + } + std::transform(rep->products_2->begin(), rep->products_2->end(), std::back_inserter(rep->products), [this, &rep, kernel](IfcUtil::IfcBaseClass* prod) { + auto prod_item = kernel->mapping()->map(prod); + return std::make_pair(prod->as(), ifcopenshell::geometry::taxonomy::cast(prod_item)->matrix); + }); + } else { + } + + auto product_node = rep->products.front(); + const IfcUtil::IfcBaseEntity* product = product_node.first; + const auto& place = product_node.second; + + Logger::SetProduct(product); + + IfcGeom::BRepElement* brep = static_cast(decorate_with_cache_(GeometrySerializer::READ_BREP, (std::string)product->get("GlobalId"), std::to_string(rep->item->instance->as()->id()), [kernel, settings, product, place, rep]() { + return kernel->create_brep_for_representation_and_product(rep->item, product, place); + })); + + if (!brep) { + return; + } + + auto elem = process_based_on_settings(settings, brep); + if (!elem) { + return; + } + + rep->breps = { brep }; + rep->elements = { elem }; + + for (auto it = rep->products.begin() + 1; it != rep->products.end(); ++it) { + const auto& p = *it; + const IfcUtil::IfcBaseEntity* product2 = p.first; + const auto& place2 = p.second; + + IfcGeom::BRepElement* brep2 = static_cast(decorate_with_cache_(GeometrySerializer::READ_BREP, (std::string)product2->get("GlobalId"), std::to_string(rep->item->instance->as()->id()), [kernel, settings, product2, place2, brep]() { + return kernel->create_brep_for_processed_representation(product2, place2, brep); + })); + if (brep2) { + auto elem2 = process_based_on_settings(settings, brep2, dynamic_cast(elem)); + if (elem2) { + rep->breps.push_back(brep2); + rep->elements.push_back(elem2); + } + } + } +} + +IfcGeom::Element* IfcGeom::Iterator::process_based_on_settings(ifcopenshell::geometry::Settings settings, IfcGeom::BRepElement* elem, IfcGeom::TriangulationElement* previous) +{ + if (settings.get().get() == ifcopenshell::geometry::settings::SERIALIZED) { + try { + return new IfcGeom::SerializedElement(*elem); + } catch (...) { + Logger::Message(Logger::LOG_ERROR, "Getting a serialized element from model failed."); + return nullptr; + } + } else if (settings.get().get() == ifcopenshell::geometry::settings::TRIANGULATED) { + // the part before the hyphen is the representation id + auto gid2 = elem->geometry().id(); + auto hyphen = gid2.find("-"); + if (hyphen != std::string::npos) { + gid2 = gid2.substr(0, hyphen); + } + + return decorate_with_cache_(GeometrySerializer::READ_TRIANGULATION, elem->guid(), gid2, [elem, previous]() { + try { + if (!previous) { + return new TriangulationElement(*elem); + } else { + return new TriangulationElement(*elem, previous->geometry_pointer()); + } + } catch (...) { + Logger::Message(Logger::LOG_ERROR, "Getting a triangulation element from model failed."); + } + return (TriangulationElement*)nullptr; + }); + } else { + return elem; + } +} + +bool IfcGeom::Iterator::wait_for_element() { + while (true) { + size_t s; + { + std::lock_guard lk(element_ready_mutex_); + s = all_processed_elements_.size(); + } + if (s > async_elements_returned_) { + ++async_elements_returned_; + return true; + } else if (finished_) { + return false; + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } +} + +void IfcGeom::Iterator::log_timepoints() const { + using std::chrono::high_resolution_clock; + using std::chrono::duration; + using namespace std::string_literals; + + std::array labels = { + "Initializing mapping"s, + "Performing mapping"s, + "Geometry interpretation"s + }; + + for (auto it = time_points.begin() + 1; it != time_points.end(); ++it) { + auto jt = it - 1; + duration ms_double = (*it) - (*jt); + Logger::Notice(labels[std::distance(time_points.begin(), jt)] + " took " + std::to_string(ms_double.count()) + "ms"); + } +} + +/// Moves to the next shape representation, create its geometry, and returns the associated product. +/// Use get() to retrieve the created geometry. +const IfcUtil::IfcBaseClass* IfcGeom::Iterator::next() { + using std::chrono::high_resolution_clock; + + if (*native_task_result_iterator_ != *task_result_iterator_) { + delete* native_task_result_iterator_; + } + delete* task_result_iterator_; + + if (num_threads_ != 1) { + if (!wait_for_element()) { + Logger::SetProduct(boost::none); + time_points[3] = high_resolution_clock::now(); + log_timepoints(); + return nullptr; + } + + task_result_iterator_++; + native_task_result_iterator_++; + + return (*task_result_iterator_)->product(); + } else { + // Increment the iterator over the list of products using the current + // shape representation + if (task_result_iterator_ == --all_processed_elements_.end()) { + if (!create()) { + Logger::SetProduct(boost::none); + time_points[3] = high_resolution_clock::now(); + log_timepoints(); + return nullptr; + } + } + + task_result_iterator_++; + native_task_result_iterator_++; + + return (*task_result_iterator_)->product(); + } +} + +/// Gets the representation of the current geometrical entity. +IfcGeom::Element* IfcGeom::Iterator::get() +{ + if (!initialization_outcome_) { + throw std::runtime_error("Iterator not initialized"); + } + + auto ret = *task_result_iterator_; + + // If we want to organize the element considering their hierarchy + if (settings_.get().get()) { + // We are going to build a vector with the element parents. + // First, create the parent vector + std::vector parents; + + // if the element has a parent + if (ret->parent_id() != -1) { + const IfcGeom::Element* parent_object = NULL; + bool hasParent = true; + + // get the parent + try { + parent_object = get_object(ret->parent_id()); + } catch (const std::exception& e) { + Logger::Error(e); + hasParent = false; + } + + // Add the previously found parent to the vector + if (hasParent) parents.insert(parents.begin(), parent_object); + + // We need to find all the parents + while (parent_object != NULL && hasParent && parent_object->parent_id() != -1) { + // Find the next parent + try { + parent_object = get_object(parent_object->parent_id()); + } catch (const std::exception& e) { + Logger::Error(e); + hasParent = false; + } + + // Add the previously found parent to the vector + if (hasParent) parents.insert(parents.begin(), parent_object); + + hasParent = hasParent && parent_object->parent_id() != -1; + } + + // when done push the parent list in the Element object + ret->SetParents(parents); + } + } + + return ret; +} + +const IfcGeom::Element* IfcGeom::Iterator::get_object(int id) { + ifcopenshell::geometry::taxonomy::matrix4::ptr m4; + int parent_id = -1; + std::string instance_type, product_name, product_guid; + IfcUtil::IfcBaseEntity* ifc_product = 0; + + try { + ifc_product = ifc_file->instance_by_id(id)->as(); + instance_type = ifc_product->declaration().name(); + + if (ifc_product->declaration().is("IfcRoot")) { + product_guid = (std::string)ifc_product->get("GlobalId"); + product_name = ifc_product->get_value("Name", ""); + } + + auto parent_object = converter_->mapping()->get_decomposing_entity(ifc_product); + if (parent_object) { + parent_id = parent_object->id(); + } + + // fails in case of IfcProject + auto mapped = converter_->mapping()->map(ifc_product); + auto casted = mapped ? ifcopenshell::geometry::taxonomy::dcast(mapped) : nullptr; + + if (casted) { + m4 = casted->matrix; + } + } catch (const std::exception& e) { + Logger::Error(e); + } +#ifdef IFOPSH_WITH_OPENCASCADE + catch (const Standard_Failure& e) { + if (e.GetMessageString() && strlen(e.GetMessageString())) { + Logger::Error(e.GetMessageString()); + } else { + Logger::Error("Unknown error returning product"); + } + } +#endif + catch (...) { + Logger::Error("Unknown error returning product"); + } + + Element* ifc_object = new Element(settings_, id, parent_id, product_name, instance_type, product_guid, "", m4, ifc_product); + return ifc_object; +} + +const IfcUtil::IfcBaseClass* IfcGeom::Iterator::create() { + const IfcUtil::IfcBaseClass* product = nullptr; + try { + product = create_shape_model_for_next_entity(); + } catch (const std::exception& e) { + Logger::Error(e); + had_error_processing_elements_ = true; + } +#ifdef IFOPSH_WITH_OPENCASCADE + catch (const Standard_Failure& e) { + if (e.GetMessageString() && strlen(e.GetMessageString())) { + Logger::Error(e.GetMessageString()); + } else { + Logger::Error("Unknown error creating geometry"); + } + had_error_processing_elements_ = true; + } +#endif + catch (...) { + Logger::Error("Unknown error creating geometry"); + had_error_processing_elements_ = true; + } + return product; +} + +IfcGeom::Iterator::~Iterator() { + if (num_threads_ != 1) { + terminating_ = true; + + if (init_future_.valid()) { + init_future_.wait(); + } + } + + for (auto& k : kernel_pool) { + delete k; + } + + if (task_result_ptr_initialized) { + while (task_result_iterator_ != --all_processed_elements_.end()) { + if (*native_task_result_iterator_ != *task_result_iterator_) { + delete* native_task_result_iterator_; + } + delete* task_result_iterator_++; + native_task_result_iterator_++; + } + } + + delete converter_; +} diff --git a/src/ifcgeom/Iterator.h b/src/ifcgeom/Iterator.h index 45f850acfd..efabdf37bd 100644 --- a/src/ifcgeom/Iterator.h +++ b/src/ifcgeom/Iterator.h @@ -123,7 +123,6 @@ namespace IfcGeom { std::mutex element_ready_mutex_; bool task_result_ptr_initialized = false; - // ? size_t async_elements_returned_ = 0; size_t task_result_index_ = 0; @@ -155,357 +154,13 @@ namespace IfcGeom { // Should not be destructed because, destructor is blocking std::future init_future_; + std::mutex caching_mutex_; std::array time_points; - /// @todo public/private sections all over the place: move all public to the beginning of the class - public: - void set_cache(GeometrySerializer* cache) { cache_ = cache; } - - const std::string& unit_name() const { return converter_->mapping()->get_length_unit_name(); } - double unit_magnitude() const { return converter_->mapping()->get_length_unit(); } - // Check if error occurred during iterator initialization or iteration over elements. - bool had_error_processing_elements() const { return had_error_processing_elements_; } - - boost::optional initialization_outcome_; - - /** - * @return Returns true if the iterator is initialized with any elements, false otherwise. - * - * @note - * - A true return value does not guarantee successful initialization of all elements. - * Some elements may have failed to initialize. Check had_error_processing_elements() - * to see whether there were errors during the initialization. - * - * - For non-concurrent iterators, a false return may occur if initialization of the first - * element fails, even if subsequent elements could be initialized successfully. - */ - bool initialize() { - using std::chrono::high_resolution_clock; - - if (initialization_outcome_) { - return *initialization_outcome_; - } - - time_points[0] = high_resolution_clock::now(); - std::vector reps; - if (num_threads_ != 1) { - // @todo this shouldn't be necessary with properly immutable taxonomy items - converter_->mapping()->use_caching() = false; - } - try { - converter_->mapping()->get_representations(reps, filters_); - } catch (const std::exception& e) { - Logger::Error(e); - } - time_points[1] = high_resolution_clock::now(); - - for (auto& task : reps) { - geometry_conversion_result res; - res.index = task.index; - if (!settings_.get().get()) { - res.representation = task.representation; - res.products_2 = task.products; - } else { - res.item = converter_->mapping()->map(task.representation); - if (!res.item) { - continue; - } - std::transform(task.products->begin(), task.products->end(), std::back_inserter(res.products), [this, &res](IfcUtil::IfcBaseClass* prod) { - auto prod_item = converter_->mapping()->map(prod); - return std::make_pair(prod->as(), ifcopenshell::geometry::taxonomy::cast(prod_item)->matrix); - }); - } - tasks_.push_back(res); - } - - if (settings_.get().get() && settings_.get().get()) { - std::unordered_map< - ifcopenshell::geometry::taxonomy::item::ptr, - std::vector>> folded; - - for (auto& r : tasks_) { - auto i = r.item; - - Eigen::Matrix4d m4 = Eigen::Matrix4d::Identity(); - - while (auto col = std::dynamic_pointer_cast(i)) { - if (col->children.size() == 1) { - if (col->matrix) { - m4 *= col->matrix->ccomponents(); - } - i = col->children[0]; - } else { - break; - } - } - - for (auto& p : r.products) { - auto pl = ifcopenshell::geometry::taxonomy::matrix4::ptr(p.second->clone_()); - pl->components() *= m4; - folded[i].push_back( - { p.first, pl } - ); - } - } - - if (folded.size() < tasks_.size()) { - auto old_size = tasks_.size(); - tasks_.clear(); - size_t i = 0; - for (auto& p : folded) { - tasks_.emplace_back(); - tasks_.back().index = i++; - tasks_.back().item = p.first; - tasks_.back().products = p.second; - } - Logger::Notice("Merged " + std::to_string(old_size) + " tasks into " + std::to_string(tasks_.size()) + " tasks due to permissive shape reuse"); - } - } - - size_t num_products = 0; - for (auto& r : tasks_) { - num_products += !settings_.get().get() ? r.products_2->size() : r.products.size(); - } - - time_points[2] = high_resolution_clock::now(); - - /* - // What to do, map representation and product individually? - // There needs to be two options, mapped item respecting (does that still work?), and optimized based on topology sorting. - // Or is the sorting not necessary if we just cache? - - std::vector items; - std::map placements; - std::transform(products.begin(), products.end(), std::back_inserter(items), [this, &placements](IfcUtil::IfcBaseClass* p) { - auto item = converter_->mapping()->map(p); - // Product placements do not affect item reuse and should temporarily be swapped to identity - if (item) { - std::swap(placements[item], ((taxonomy::geom_ptr)item)->matrix); - } - return item; - }); - items.erase(std::remove(items.begin(), items.end(), nullptr), items.end()); - std::sort(items.begin(), items.end(), taxonomy::less); - auto it = items.begin(); - while (it < items.end()) { - auto jt = std::upper_bound(it, items.end(), *it, taxonomy::less); - geometry_conversion_result r; - r.item = *it; - std::transform(it, jt, std::back_inserter(r.products), [&r, &placements](taxonomy::ptr product_node) { - return std::make_pair((IfcUtil::IfcBaseEntity*) product_node->instance, placements[product_node]); - }); - tasks_.push_back(r); - it = jt; - } - */ - - Logger::Notice("Created " + boost::lexical_cast(tasks_.size()) + " tasks for " + boost::lexical_cast(num_products) + " products"); - - if (tasks_.size() == 0) { - Logger::Warning("No representations encountered, aborting"); - initialization_outcome_.reset(false); - } else { - - task_iterator_ = tasks_.begin(); - - task_result_index_ = 0; - done = 0; - total = (int) tasks_.size(); - - if (num_threads_ != 1) { - init_future_ = std::async(std::launch::async, [this]() { process_concurrently(); }); - - // wait for the first element, because after init(), get() can be called. - // so the element conversion must succeed - initialization_outcome_ = wait_for_element(); - } else { - initialization_outcome_ = create(); - } - } - - return *initialization_outcome_; - } - - size_t processed_ = 0; - - void process_finished_rep(geometry_conversion_result* rep) { - if (rep->elements.empty()) { - return; - } - - std::lock_guard lk(element_ready_mutex_); - - all_processed_elements_.insert(all_processed_elements_.end(), rep->elements.begin(), rep->elements.end()); - all_processed_native_elements_.insert(all_processed_native_elements_.end(), rep->breps.begin(), rep->breps.end()); - - if (!task_result_ptr_initialized) { - task_result_iterator_ = all_processed_elements_.begin(); - native_task_result_iterator_ = all_processed_native_elements_.begin(); - task_result_ptr_initialized = true; - } - - progress_ = (int) (++processed_ * 100 / tasks_.size()); - } - - void process_concurrently() { - size_t conc_threads = num_threads_; - if (conc_threads > tasks_.size()) { - conc_threads = tasks_.size(); - } - - kernel_pool.reserve(conc_threads); - for (unsigned i = 0; i < conc_threads; ++i) { - kernel_pool.push_back(new ifcopenshell::geometry::Converter(geometry_library_, ifc_file, settings_)); - } - - std::vector> threadpool; - - for (auto& rep : tasks_) { - ifcopenshell::geometry::Converter* K = nullptr; - if (threadpool.size() < kernel_pool.size()) { - K = kernel_pool[threadpool.size()]; - } - - while (threadpool.size() == conc_threads) { - for (int i = 0; i < (int)threadpool.size(); i++) { - auto& fu = threadpool[i]; - std::future_status status; - status = fu.wait_for(std::chrono::seconds(0)); - if (status == std::future_status::ready) { - process_finished_rep(fu.get()); - - std::swap(threadpool[i], threadpool.back()); - threadpool.pop_back(); - std::swap(kernel_pool[i], kernel_pool.back()); - K = kernel_pool.back(); - break; - } // if - } // for - } // while - - std::future fu = std::async( - std::launch::async, [this]( - ifcopenshell::geometry::Converter* kernel, - ifcopenshell::geometry::Settings settings, - geometry_conversion_result* rep) { - // Catch exceptions to be safe from freezing the iterator. - try { - this->create_element_(kernel, settings, rep); - } catch (const std::exception& e) { - Logger::Error( - std::string("Exception '") + e.what() + - std::string("' occurred while iterator was creating a shape: "), - rep->item->instance - ); - had_error_processing_elements_ = true; - } catch (...) { - Logger::Error( - "Unknown exception occurred while iteartor was creating a shape: ", - rep->item->instance - ); - had_error_processing_elements_ = true; - } - return rep; - }, - K, - std::ref(settings_), - &rep); - - if (terminating_) { - break; - } - - threadpool.emplace_back(std::move(fu)); - } - - for (auto& fu : threadpool) { - process_finished_rep(fu.get()); - } - - finished_ = true; - - Logger::SetProduct(boost::none); - - if (!terminating_) { - Logger::Status("\rDone creating geometry (" + boost::lexical_cast(all_processed_elements_.size()) + - " objects) "); - } - } - - /// Computes model's bounding box (bounds_min and bounds_max). - /// @note Can take several minutes for large files. - void compute_bounds(bool with_geometry) - { - for (int i = 0; i < 3; ++i) { - bounds_min_.components()(i) = std::numeric_limits::infinity(); - bounds_max_.components()(i) = -std::numeric_limits::infinity(); - } - - if (with_geometry) { - size_t num_created = 0; - do { - IfcGeom::Element* geom_object = get(); - const IfcGeom::TriangulationElement* o = static_cast(geom_object); - const IfcGeom::Representation::Triangulation& mesh = o->geometry(); - auto mat = o->transformation().data()->ccomponents(); - Eigen::Vector4d vec, transformed; - - for (typename std::vector::const_iterator it = mesh.verts().begin(); it != mesh.verts().end();) { - const double& x = *(it++); - const double& y = *(it++); - const double& z = *(it++); - vec << x, y, z, 1.; - transformed = mat * vec; - - for (int i = 0; i < 3; ++i) { - bounds_min_.components()(i) = std::min(bounds_min_.components()(i), transformed(i)); - bounds_max_.components()(i) = std::max(bounds_max_.components()(i), transformed(i)); - } - } - } while (++num_created, next()); - } else { - std::vector reps; - converter_->mapping()->get_representations(reps, filters_); - - std::vector products; - for (auto& r : reps) { - std::copy(r.products->begin(), r.products->end(), std::back_inserter(products)); - } - - for (auto& product : products) { - auto prod_item = converter_->mapping()->map(product); - auto vec = ifcopenshell::geometry::taxonomy::cast(prod_item)->matrix->translation_part(); - - for (int i = 0; i < 3; ++i) { - bounds_min_.components()(i) = std::min(bounds_min_.components()(i), vec(i)); - bounds_max_.components()(i) = std::max(bounds_max_.components()(i), vec(i)); - } - } - } - } - - int progress() const { - return progress_; - } - - std::string getLog() const { return Logger::GetLog(); } - - IfcParse::IfcFile* file() const { return ifc_file; } - - const std::vector& filters() const { return filters_; } - std::vector& filters() { return filters_; } - - const ifcopenshell::geometry::taxonomy::point3& bounds_min() const { return bounds_min_; } - const ifcopenshell::geometry::taxonomy::point3& bounds_max() const { return bounds_max_; } - - private: - - std::mutex caching_mutex_; - template Element* decorate_with_cache_(GeometrySerializer::read_type rt, const std::string& product_guid, const std::string& representation_id, Fn f) { - + bool read_from_cache = false; Element* element = nullptr; @@ -529,357 +184,34 @@ namespace IfcGeom { std::lock_guard lk(caching_mutex_); if (rt == GeometrySerializer::READ_TRIANGULATION) { - cache_->write((IfcGeom::TriangulationElement*) element); + cache_->write((IfcGeom::TriangulationElement*)element); } else { cache_->write((IfcGeom::BRepElement*)element); - } + } } #endif return element; } - const IfcUtil::IfcBaseClass* create_shape_model_for_next_entity() { - geometry_conversion_result* task = nullptr; - for (; task_iterator_ < tasks_.end();) { - task = &*task_iterator_++; - create_element_(converter_, settings_, task); - if (task->elements.empty()) { - task = nullptr; - } else { - break; - } - } - if (task) { - process_finished_rep(task); - return task->item->instance->as(); - } else { - return nullptr; - } - } + const IfcUtil::IfcBaseClass* create_shape_model_for_next_entity(); void create_element_( ifcopenshell::geometry::Converter* kernel, ifcopenshell::geometry::Settings settings, - geometry_conversion_result* rep) - { - if (!settings_.get().get()) { - rep->item = kernel->mapping()->map(rep->representation); - if (!rep->item) { - return; - } - std::transform(rep->products_2->begin(), rep->products_2->end(), std::back_inserter(rep->products), [this, &rep, kernel](IfcUtil::IfcBaseClass* prod) { - auto prod_item = kernel->mapping()->map(prod); - return std::make_pair(prod->as(), ifcopenshell::geometry::taxonomy::cast(prod_item)->matrix); - }); - } else { - } - - auto product_node = rep->products.front(); - const IfcUtil::IfcBaseEntity* product = product_node.first; - const auto& place = product_node.second; - - Logger::SetProduct(product); - - IfcGeom::BRepElement* brep = static_cast(decorate_with_cache_(GeometrySerializer::READ_BREP, (std::string)product->get("GlobalId"), std::to_string(rep->item->instance->as()->id()), [kernel, settings, product, place, rep]() { - return kernel->create_brep_for_representation_and_product(rep->item, product, place); - })); - - if (!brep) { - return; - } - - auto elem = process_based_on_settings(settings, brep); - if (!elem) { - return; - } - - rep->breps = { brep }; - rep->elements = { elem }; - - for (auto it = rep->products.begin() + 1; it != rep->products.end(); ++it) { - const auto& p = *it; - const IfcUtil::IfcBaseEntity* product2 = p.first; - const auto& place2 = p.second; - - IfcGeom::BRepElement* brep2 = static_cast(decorate_with_cache_(GeometrySerializer::READ_BREP, (std::string)product2->get("GlobalId"), std::to_string(rep->item->instance->as()->id()), [kernel, settings, product2, place2, brep]() { - return kernel->create_brep_for_processed_representation(product2, place2, brep); - })); - if (brep2) { - auto elem2 = process_based_on_settings(settings, brep2, dynamic_cast(elem)); - if (elem2) { - rep->breps.push_back(brep2); - rep->elements.push_back(elem2); - } - } - } - } + geometry_conversion_result* rep); IfcGeom::Element* process_based_on_settings( ifcopenshell::geometry::Settings settings, IfcGeom::BRepElement* elem, - IfcGeom::TriangulationElement* previous = nullptr) - { - if (settings.get().get() == ifcopenshell::geometry::settings::SERIALIZED) { - try { - return new IfcGeom::SerializedElement(*elem); - } catch (...) { - Logger::Message(Logger::LOG_ERROR, "Getting a serialized element from model failed."); - return nullptr; - } - } else if (settings.get().get() == ifcopenshell::geometry::settings::TRIANGULATED) { - // the part before the hyphen is the representation id - auto gid2 = elem->geometry().id(); - auto hyphen = gid2.find("-"); - if (hyphen != std::string::npos) { - gid2 = gid2.substr(0, hyphen); - } + IfcGeom::TriangulationElement* previous = nullptr); - return decorate_with_cache_(GeometrySerializer::READ_TRIANGULATION, elem->guid(), gid2, [elem, previous]() { - try { - if (!previous) { - return new TriangulationElement(*elem); - } else { - return new TriangulationElement(*elem, previous->geometry_pointer()); - } - } catch (...) { - Logger::Message(Logger::LOG_ERROR, "Getting a triangulation element from model failed."); - } - return (TriangulationElement*)nullptr; - }); - } else { - return elem; - } - } + bool wait_for_element(); - bool wait_for_element() { - while (true) { - size_t s; - { - std::lock_guard lk(element_ready_mutex_); - s = all_processed_elements_.size(); - } - if (s > async_elements_returned_) { - ++async_elements_returned_; - return true; - } else if (finished_) { - return false; - } else { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - } - } - - void log_timepoints() const { - using std::chrono::high_resolution_clock; - using std::chrono::duration; - using namespace std::string_literals; - - std::array labels = { - "Initializing mapping"s, - "Performing mapping"s, - "Geometry interpretation"s - }; - - for (auto it = time_points.begin() + 1; it != time_points.end(); ++it) { - auto jt = it - 1; - duration ms_double = (*it) - (*jt); - Logger::Notice(labels[std::distance(time_points.begin(), jt)] + " took " + std::to_string(ms_double.count()) + "ms"); - } - } + void log_timepoints() const; + /// @todo public/private sections all over the place: move all public to the beginning of the class public: - /// Returns what would be the product for the next shape representation - /// @todo Double-check and test the impl. - //IfcSchema::IfcProduct* peek_next() const - //{ - // if (ifcproducts && ifcproduct_iterator + 1 != ifcproducts->end()){ - // return *(ifcproduct_iterator + 1); - // } else { - // return 0; - // } - //} - - /// @todo Would this be as simple as the following code? - //void skip_next() { if (ifcproducts) { ++ifcproduct_iterator; } } - - /// Moves to the next shape representation, create its geometry, and returns the associated product. - /// Use get() to retrieve the created geometry. - const IfcUtil::IfcBaseClass* next() { - using std::chrono::high_resolution_clock; - - if (*native_task_result_iterator_ != *task_result_iterator_) { - delete* native_task_result_iterator_; - } - delete *task_result_iterator_; - - if (num_threads_ != 1) { - if (!wait_for_element()) { - Logger::SetProduct(boost::none); - time_points[3] = high_resolution_clock::now(); - log_timepoints(); - return nullptr; - } - - task_result_iterator_++; - native_task_result_iterator_++; - - return (*task_result_iterator_)->product(); - } else { - // Increment the iterator over the list of products using the current - // shape representation - if (task_result_iterator_ == --all_processed_elements_.end()) { - if (!create()) { - Logger::SetProduct(boost::none); - time_points[3] = high_resolution_clock::now(); - log_timepoints(); - return nullptr; - } - } - - task_result_iterator_++; - native_task_result_iterator_++; - - return (*task_result_iterator_)->product(); - } - } - - /// Gets the representation of the current geometrical entity. - Element* get() - { - if (!initialization_outcome_) { - throw std::runtime_error("Iterator not initialized"); - } - - auto ret = *task_result_iterator_; - - // If we want to organize the element considering their hierarchy - if (settings_.get().get()) - { - // We are going to build a vector with the element parents. - // First, create the parent vector - std::vector parents; - - // if the element has a parent - if (ret->parent_id() != -1) - { - const IfcGeom::Element* parent_object = NULL; - bool hasParent = true; - - // get the parent - try { - parent_object = get_object(ret->parent_id()); - } catch (const std::exception& e) { - Logger::Error(e); - hasParent = false; - } - - // Add the previously found parent to the vector - if (hasParent) parents.insert(parents.begin(), parent_object); - - // We need to find all the parents - while (parent_object != NULL && hasParent && parent_object->parent_id() != -1) - { - // Find the next parent - try { - parent_object = get_object(parent_object->parent_id()); - } catch (const std::exception& e) { - Logger::Error(e); - hasParent = false; - } - - // Add the previously found parent to the vector - if (hasParent) parents.insert(parents.begin(), parent_object); - - hasParent = hasParent && parent_object->parent_id() != -1; - } - - // when done push the parent list in the Element object - ret->SetParents(parents); - } - } - - return ret; - } - - /// Gets the native (Open Cascade or CGAL) representation of the current geometrical entity. - BRepElement* get_native() - { - return *native_task_result_iterator_; - } - - const Element* get_object(int id) { - ifcopenshell::geometry::taxonomy::matrix4::ptr m4; - int parent_id = -1; - std::string instance_type, product_name, product_guid; - IfcUtil::IfcBaseEntity* ifc_product = 0; - - try { - ifc_product = ifc_file->instance_by_id(id)->as(); - instance_type = ifc_product->declaration().name(); - - if (ifc_product->declaration().is("IfcRoot")) { - product_guid = (std::string) ifc_product->get("GlobalId"); - product_name = ifc_product->get_value("Name", ""); - } - - auto parent_object = converter_->mapping()->get_decomposing_entity(ifc_product); - if (parent_object) { - parent_id = parent_object->id(); - } - - // fails in case of IfcProject - auto mapped = converter_->mapping()->map(ifc_product); - auto casted = mapped ? ifcopenshell::geometry::taxonomy::dcast(mapped) : nullptr; - - if (casted) { - m4 = casted->matrix; - } - } catch (const std::exception& e) { - Logger::Error(e); - } -#ifdef IFOPSH_WITH_OPENCASCADE - catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error returning product"); - } - } -#endif - catch (...) { - Logger::Error("Unknown error returning product"); - } - - Element* ifc_object = new Element(settings_, id, parent_id, product_name, instance_type, product_guid, "", m4, ifc_product); - return ifc_object; - } - - const IfcUtil::IfcBaseClass* create() { - const IfcUtil::IfcBaseClass* product = nullptr; - try { - product = create_shape_model_for_next_entity(); - } catch (const std::exception& e) { - Logger::Error(e); - had_error_processing_elements_ = true; - } -#ifdef IFOPSH_WITH_OPENCASCADE - catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error creating geometry"); - } - had_error_processing_elements_ = true; - } -#endif - catch (...) { - Logger::Error("Unknown error creating geometry"); - had_error_processing_elements_ = true; - } - return product; - } - Iterator(const std::string& geometry_library, const ifcopenshell::geometry::Settings& settings, IfcParse::IfcFile* file, const std::vector& filters, int num_threads) : settings_(settings) , ifc_file(file) @@ -927,31 +259,70 @@ namespace IfcGeom { { } - ~Iterator() { - if (num_threads_ != 1) { - terminating_ = true; + ~Iterator(); - if (init_future_.valid()) { - init_future_.wait(); - } - } - - for (auto& k : kernel_pool) { - delete k; - } + void set_cache(GeometrySerializer* cache) { cache_ = cache; } - if (task_result_ptr_initialized) { - while (task_result_iterator_ != --all_processed_elements_.end()) { - if (*native_task_result_iterator_ != *task_result_iterator_) { - delete* native_task_result_iterator_; - } - delete *task_result_iterator_++; - native_task_result_iterator_++; - } - } + const std::string& unit_name() const { return converter_->mapping()->get_length_unit_name(); } + double unit_magnitude() const { return converter_->mapping()->get_length_unit(); } + // Check if error occurred during iterator initialization or iteration over elements. + bool had_error_processing_elements() const { return had_error_processing_elements_; } - delete converter_; + boost::optional initialization_outcome_; + + /** + * @return Returns true if the iterator is initialized with any elements, false otherwise. + * + * @note + * - A true return value does not guarantee successful initialization of all elements. + * Some elements may have failed to initialize. Check had_error_processing_elements() + * to see whether there were errors during the initialization. + * + * - For non-concurrent iterators, a false return may occur if initialization of the first + * element fails, even if subsequent elements could be initialized successfully. + */ + bool initialize(); + + size_t processed_ = 0; + + void process_finished_rep(geometry_conversion_result* rep); + + void process_concurrently(); + + /// Computes model's bounding box (bounds_min and bounds_max). + /// @note Can take several minutes for large files. + void compute_bounds(bool with_geometry); + + int progress() const { + return progress_; } + + std::string getLog() const { return Logger::GetLog(); } + + IfcParse::IfcFile* file() const { return ifc_file; } + + const std::vector& filters() const { return filters_; } + std::vector& filters() { return filters_; } + + const ifcopenshell::geometry::taxonomy::point3& bounds_min() const { return bounds_min_; } + const ifcopenshell::geometry::taxonomy::point3& bounds_max() const { return bounds_max_; } + + /// Moves to the next shape representation, create its geometry, and returns the associated product. + /// Use get() to retrieve the created geometry. + const IfcUtil::IfcBaseClass* next(); + + /// Gets the representation of the current geometrical entity. + Element* get(); + + /// Gets the native (Open Cascade or CGAL) representation of the current geometrical entity. + BRepElement* get_native() + { + return *native_task_result_iterator_; + } + + const Element* get_object(int id); + + const IfcUtil::IfcBaseClass* create(); }; }