diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index f02bb715ea..a0af85b6ff 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -820,6 +820,13 @@ int main(int argc, char** argv) { } } + // The OS will clean up for us if there is a leak + geometry_settings.get().value = true; + + if (geometry_settings.get().get()) { + geometry_settings.get().value = true; + } + if (geometry_settings.get().get() && output_extension != DAE && output_extension != USD && output_extension != USDA && output_extension != USDC) { cerr_ << "[Error] --use-element-hierarchy can be used only with .dae or .usd output.\n"; /// @todo Lots of duplicate error-and-exit code. diff --git a/src/ifcgeom/AbstractKernel.cpp b/src/ifcgeom/AbstractKernel.cpp index ef67850955..d88acf0a2e 100644 --- a/src/ifcgeom/AbstractKernel.cpp +++ b/src/ifcgeom/AbstractKernel.cpp @@ -22,6 +22,16 @@ using namespace ifcopenshell::geometry; bool ifcopenshell::geometry::kernels::AbstractKernel::convert(const taxonomy::ptr item, IfcGeom::ConversionResults& results) { + if (settings_.get().get()) { + auto it = cache_.find(item); + if (it != cache_.end()) { + results = it->second; + Logger::Notice("Cache hit #" + std::to_string(item->instance->as()->id()) + + " -> #" + std::to_string(it->first->instance->as()->id())); + return true; + } + } + auto with_exception_handling = [&](auto fn) { try { return fn(); @@ -44,11 +54,18 @@ bool ifcopenshell::geometry::kernels::AbstractKernel::convert(const taxonomy::pt } }; + bool res; if (propagate_exceptions) { - return without_exception_handling(process_with_upgrade); + res = without_exception_handling(process_with_upgrade); } else { - return with_exception_handling(process_with_upgrade); + res = with_exception_handling(process_with_upgrade); } + + if (settings_.get().get() && res) { + cache_.insert({ item, results }); + } + + return res; } const Settings& ifcopenshell::geometry::kernels::AbstractKernel::settings() const diff --git a/src/ifcgeom/AbstractKernel.h b/src/ifcgeom/AbstractKernel.h index 3246f6ad5c..ed4985c056 100644 --- a/src/ifcgeom/AbstractKernel.h +++ b/src/ifcgeom/AbstractKernel.h @@ -34,6 +34,8 @@ namespace ifcopenshell { namespace geometry { namespace kernels { class IFC_GEOM_API AbstractKernel { + private: + std::unordered_map cache_; protected: std::string geometry_library_; Settings settings_; diff --git a/src/ifcgeom/ConversionSettings.h b/src/ifcgeom/ConversionSettings.h index c5361b30c0..20132250cd 100644 --- a/src/ifcgeom/ConversionSettings.h +++ b/src/ifcgeom/ConversionSettings.h @@ -344,6 +344,12 @@ namespace ifcopenshell { static constexpr bool defaultvalue = false; }; + struct PermissiveShapeReuse : public SettingBase { + static constexpr const char* const name = "permissive-shape-reuse"; + static constexpr const char* const description = "Traverse geometry-level transformations and apply to product-level placement in order to increase reuse of geometries"; + static constexpr bool defaultvalue = false; + }; + struct ForceSpaceTransparency : public SettingBase { static constexpr const char* const name = "force-space-transparency"; static constexpr const char* const description = "Overrides transparency of spaces in geometry output."; @@ -423,6 +429,18 @@ namespace ifcopenshell { static constexpr const char* const description = "Try to emit original edge face boundary edges instead of recomputed ones based on face normal. Falls back to triangulated data in case of boolean operands and faces with holes."; static constexpr bool defaultvalue = false; }; + + struct OcctNoCleanTriangulation : public SettingBase { + static constexpr const char* const name = "no-clean-triangulation"; + static constexpr const char* const description = "Don't clean triangulations, might cause memory leaks"; + static constexpr bool defaultvalue = false; + }; + + struct CacheShapes : public SettingBase { + static constexpr const char* const name = "cache-shapes"; + static constexpr const char* const description = "Experimental as not all topology hash functions fully implemented"; + static constexpr bool defaultvalue = false; + }; } namespace impl { @@ -598,7 +616,7 @@ namespace ifcopenshell { }; class IFC_GEOM_API Settings : public SettingsContainer< - std::tuple + std::tuple > {}; } diff --git a/src/ifcgeom/Iterator.h b/src/ifcgeom/Iterator.h index d1d3a916a3..45f850acfd 100644 --- a/src/ifcgeom/Iterator.h +++ b/src/ifcgeom/Iterator.h @@ -219,6 +219,50 @@ namespace IfcGeom { 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(); diff --git a/src/ifcgeom/kernels/cgal/CgalConversionResult.cpp b/src/ifcgeom/kernels/cgal/CgalConversionResult.cpp index e7a3b2a9ce..77bfb0de93 100644 --- a/src/ifcgeom/kernels/cgal/CgalConversionResult.cpp +++ b/src/ifcgeom/kernels/cgal/CgalConversionResult.cpp @@ -47,8 +47,6 @@ namespace { }; bool are_facets_coplanar(const Facet_const_handle& f1, const Facet_const_handle& f2) { - // Function to determine if two facets are coplanar - // You can use the normal vectors and the equation of the planes to determine coplanarity auto normal_1 = CGAL::normal(f1->halfedge()->vertex()->point(), f1->halfedge()->next()->vertex()->point(), f1->halfedge()->next()->next()->vertex()->point()); @@ -69,8 +67,8 @@ namespace { continue; } - // Create a new component for coplanar facets - std::set component; + components.emplace_back(); + auto& component = components.back(); std::queue queue; queue.push(face); @@ -82,18 +80,15 @@ namespace { component.insert(current); - // Iterate over neighboring facets Halfedge_around_facet_circulator he = current->facet_begin(); do { Facet_const_handle neighbour = he->opposite()->face(); - if (neighbour != nullptr && visited.find(neighbour) == visited.end() && are_facets_coplanar(current, neighbour)) { + if (visited.find(neighbour) == visited.end() && neighbour != nullptr && visited.find(neighbour) == visited.end() && are_facets_coplanar(current, neighbour)) { queue.push(neighbour); visited.insert(neighbour); } } while (++he != current->facet_begin()); } - - components.push_back(component); } } } @@ -184,20 +179,30 @@ void ifcopenshell::geometry::CgalShape::to_nef() const { #endif void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const { - // Copy is made because triangulate_faces() obviously does not accept a const argument - // ... also becuase of transforming the vertex positions, right? - cgal_shape_t s = *this; + const bool all_triangles = std::all_of(shape_->facets_begin(), shape_->facets_end(), [](auto f) { return f.is_triangle(); }); + const bool has_iden_transform = place.is_identity(); + + std::unique_ptr shape_copy_holder; + cgal_shape_t* shape_to_use; + + if (!all_triangles || !has_iden_transform) { + // A copy is made when triangulate_faces() is required or when vertex positions need be transformed + shape_copy_holder.reset(new cgal_shape_t(*this)); + shape_to_use = shape_copy_holder.get(); + } else { + shape_to_use = &*shape_; + } const bool setting_use_original_edges = settings.get().get(); std::set> original_edges; if (setting_use_original_edges) { - for (auto it = s.edges_begin(); it != s.edges_end(); ++it) { + for (auto it = shape_to_use->edges_begin(); it != shape_to_use->edges_end(); ++it) { original_edges.insert({ it->vertex()->point(), it->prev()->vertex()->point() }); } } - if (!place.is_identity()) { + if (!has_iden_transform) { const auto& m = place.ccomponents(); // @todo check @@ -207,33 +212,33 @@ void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Sett m(2, 0), m(2, 1), m(2, 2), m(2, 3)); // Apply transformation - for (auto &vertex : s.vertex_handles()) { + for (auto &vertex : shape_to_use->vertex_handles()) { vertex->point() = vertex->point().transform(trsf); } } - if (!std::all_of(s.facets_begin(), s.facets_end(), [](auto f) { return f.is_triangle(); })) { - if (!s.is_valid()) { + if (!all_triangles) { + if (!shape_to_use->is_valid()) { Logger::Message(Logger::LOG_ERROR, "Invalid Polyhedron_3 in object (before triangulation)"); return; } bool success = false; try { - success = CGAL::Polygon_mesh_processing::triangulate_faces(s); + success = CGAL::Polygon_mesh_processing::triangulate_faces(*shape_to_use); } catch (...) { Logger::Message(Logger::LOG_ERROR, "Triangulation crashed"); return; } - CGAL::Polygon_mesh_processing::remove_degenerate_faces(s); + CGAL::Polygon_mesh_processing::remove_degenerate_faces(*shape_to_use); if (!success) { Logger::Message(Logger::LOG_ERROR, "Triangulation failed"); return; } - if (!s.is_valid()) { + if (!shape_to_use->is_valid()) { Logger::Message(Logger::LOG_ERROR, "Invalid Polyhedron_3 in object (after triangulation)"); // return; } @@ -244,7 +249,7 @@ void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Sett std::vector> components; std::map facet_to_component; if (!setting_use_original_edges) { - partition_coplanar_components(s, components); + partition_coplanar_components(*shape_to_use, components); for (auto it = components.begin(); it != components.end(); ++it) { for (auto& f : *it) { facet_to_component[f] = it; @@ -261,7 +266,7 @@ void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Sett // CGAL::Polygon_mesh_processing::compute_normals(s, vertex_normals_map, face_normals_map); try { - CGAL::Polygon_mesh_processing::compute_face_normals(s, face_normals_map); + CGAL::Polygon_mesh_processing::compute_face_normals(*shape_to_use, face_normals_map); } catch (...) { Logger::Message(Logger::LOG_ERROR, "Face normal calculation failed"); return; @@ -275,7 +280,7 @@ void ifcopenshell::geometry::CgalShape::Triangulate(ifcopenshell::geometry::Sett std::set> registered_edges; int num_faces = 0, num_vertices = 0; - for (auto &face : faces(s)) { + for (auto &face : faces(*shape_to_use)) { if (!face->is_triangle()) { std::cout << "Warning: non-triangular face!" << std::endl; continue; diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp index 1382f1fdc9..d0a91dadf2 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp @@ -66,12 +66,28 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr // to keep track of which edges were already emitted. std::set> emitted_edges; - // Triangulate the shape - try { - BRepMesh_IncrementalMesh(shape_, settings.get().get(), false, settings.get().get()); - } catch (...) { - Logger::Message(Logger::LOG_ERROR, "Failed to triangulate shape"); - return; + // Do our own check if there are triangulations. Any will do. This is faster than the OCCT incremental check which compares the deflection tolerances and initialized a bunch of state + bool has_triangulation = false; + { + TopExp_Explorer exp; + for (exp.Init(shape_, TopAbs_FACE); exp.More(); exp.Next()) { + TopLoc_Location loc; + const Handle(Poly_Triangulation)& tri = + BRep_Tool::Triangulation(TopoDS::Face(exp.Current()), loc); + if (tri) { + has_triangulation = true; + break; + } + } + } + if (!has_triangulation) { + // Triangulate the shape + try { + BRepMesh_IncrementalMesh(shape_, settings.get().get(), false, settings.get().get()); + } catch (...) { + Logger::Message(Logger::LOG_ERROR, "Failed to triangulate shape"); + return; + } } // Iterates over the faces of the shape @@ -328,7 +344,9 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr } } - BRepTools::Clean(shape_); + if (!settings.get().get()) { + BRepTools::Clean(shape_); + } } void ifcopenshell::geometry::OpenCascadeShape::Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string& r) const { diff --git a/src/ifcgeom/taxonomy.h b/src/ifcgeom/taxonomy.h index 703504abc2..7138d9fa89 100644 --- a/src/ifcgeom/taxonomy.h +++ b/src/ifcgeom/taxonomy.h @@ -593,6 +593,24 @@ typedef item const* ptr; } }; + struct equal_functor { + bool operator()(taxonomy::item::ptr const& a, + taxonomy::item::ptr const& b) const + { + if (a == b) { + return true; + } + return !less(a, b) && !less(b, a); + } + }; + + struct hash_functor { + size_t operator()(taxonomy::item::ptr const& a) const + { + return a->hash(); + } + }; + // @todo make 4d for easier multiplication template struct cartesian_base : public item, public eigen_base {