OcctNoCleanTriangulation CacheShapes PermissiveShapeReuse setting; Cgal conditional copy during triangulation; occt cheaper check for triangulation existance #6712

This commit is contained in:
Thomas Krijnen
2025-05-18 22:14:22 +02:00
parent 404bf13a18
commit 439f9c14ce
8 changed files with 161 additions and 32 deletions
+7
View File
@@ -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<ifcopenshell::geometry::settings::OcctNoCleanTriangulation>().value = true;
if (geometry_settings.get<ifcopenshell::geometry::settings::PermissiveShapeReuse>().get()) {
geometry_settings.get<ifcopenshell::geometry::settings::NoParallelMapping>().value = true;
}
if (geometry_settings.get<ifcopenshell::geometry::settings::UseElementHierarchy>().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.
+19 -2
View File
@@ -22,6 +22,16 @@
using namespace ifcopenshell::geometry;
bool ifcopenshell::geometry::kernels::AbstractKernel::convert(const taxonomy::ptr item, IfcGeom::ConversionResults& results) {
if (settings_.get<settings::CacheShapes>().get()) {
auto it = cache_.find(item);
if (it != cache_.end()) {
results = it->second;
Logger::Notice("Cache hit #" + std::to_string(item->instance->as<IfcUtil::IfcBaseEntity>()->id()) +
" -> #" + std::to_string(it->first->instance->as<IfcUtil::IfcBaseEntity>()->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<settings::CacheShapes>().get() && res) {
cache_.insert({ item, results });
}
return res;
}
const Settings& ifcopenshell::geometry::kernels::AbstractKernel::settings() const
+2
View File
@@ -34,6 +34,8 @@ namespace ifcopenshell {
namespace geometry { namespace kernels {
class IFC_GEOM_API AbstractKernel {
private:
std::unordered_map<taxonomy::item::ptr, IfcGeom::ConversionResults, ifcopenshell::geometry::taxonomy::hash_functor, ifcopenshell::geometry::taxonomy::equal_functor> cache_;
protected:
std::string geometry_library_;
Settings settings_;
+19 -1
View File
@@ -344,6 +344,12 @@ namespace ifcopenshell {
static constexpr bool defaultvalue = false;
};
struct PermissiveShapeReuse : public SettingBase<PermissiveShapeReuse, bool> {
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<ForceSpaceTransparency, double> {
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<OcctNoCleanTriangulation, bool, true> {
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<CacheShapes, bool> {
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<MesherLinearDeflection, MesherAngularDeflection, ReorientShells, LengthUnit, PlaneUnit, Precision, OutputDimensionality, LayersetFirst, DisableBooleanResult, NoWireIntersectionCheck, NoWireIntersectionTolerance, PrecisionFactor, DebugBooleanOperations, BooleanAttempt2d, SurfaceColour, WeldVertices, UseWorldCoords, UnifyShapes, UseMaterialNames, ConvertBackUnits, ContextIds, ContextTypes, ContextIdentifiers, IteratorOutput, DisableOpeningSubtractions, ApplyDefaultMaterials, DontEmitNormals, GenerateUvs, ApplyLayerSets, UseElementHierarchy, ValidateQuantities, EdgeArrows, BuildingLocalPlacement, SiteLocalPlacement, ForceSpaceTransparency, CircleSegments, KeepBoundingBoxes, ComputeCurvature, FunctionStepType, FunctionStepParam, NoParallelMapping, ModelOffset, ModelRotation, TriangulationType, CgalEmitOriginalEdges>
std::tuple<MesherLinearDeflection, MesherAngularDeflection, ReorientShells, LengthUnit, PlaneUnit, Precision, OutputDimensionality, LayersetFirst, DisableBooleanResult, NoWireIntersectionCheck, NoWireIntersectionTolerance, PrecisionFactor, DebugBooleanOperations, BooleanAttempt2d, SurfaceColour, WeldVertices, UseWorldCoords, UnifyShapes, UseMaterialNames, ConvertBackUnits, ContextIds, ContextTypes, ContextIdentifiers, IteratorOutput, DisableOpeningSubtractions, ApplyDefaultMaterials, DontEmitNormals, GenerateUvs, ApplyLayerSets, UseElementHierarchy, ValidateQuantities, EdgeArrows, BuildingLocalPlacement, SiteLocalPlacement, ForceSpaceTransparency, CircleSegments, KeepBoundingBoxes, ComputeCurvature, FunctionStepType, FunctionStepParam, NoParallelMapping, PermissiveShapeReuse, ModelOffset, ModelRotation, TriangulationType, CgalEmitOriginalEdges, OcctNoCleanTriangulation, CacheShapes>
>
{};
}
+44
View File
@@ -219,6 +219,50 @@ namespace IfcGeom {
tasks_.push_back(res);
}
if (settings_.get<ifcopenshell::geometry::settings::NoParallelMapping>().get() && settings_.get<ifcopenshell::geometry::settings::PermissiveShapeReuse>().get()) {
std::unordered_map<
ifcopenshell::geometry::taxonomy::item::ptr,
std::vector<std::pair<const IfcUtil::IfcBaseEntity*, ifcopenshell::geometry::taxonomy::matrix4::ptr>>> folded;
for (auto& r : tasks_) {
auto i = r.item;
Eigen::Matrix4d m4 = Eigen::Matrix4d::Identity();
while (auto col = std::dynamic_pointer_cast<ifcopenshell::geometry::taxonomy::collection>(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<ifcopenshell::geometry::settings::NoParallelMapping>().get() ? r.products_2->size() : r.products.size();
@@ -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<Facet_const_handle> component;
components.emplace_back();
auto& component = components.back();
std::queue<Facet_const_handle> 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<cgal_shape_t> 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<ifcopenshell::geometry::settings::CgalEmitOriginalEdges>().get();
std::set<std::set<Kernel_::Point_3>> 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<std::set<Facet_const_handle>> components;
std::map<Facet_const_handle, typename decltype(components)::const_iterator> 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<std::pair<int, int>> 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;
@@ -66,12 +66,28 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr
// to keep track of which edges were already emitted.
std::set<std::pair<int, int>> emitted_edges;
// Triangulate the shape
try {
BRepMesh_IncrementalMesh(shape_, settings.get<settings::MesherLinearDeflection>().get(), false, settings.get<settings::MesherAngularDeflection>().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<settings::MesherLinearDeflection>().get(), false, settings.get<settings::MesherAngularDeflection>().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<settings::OcctNoCleanTriangulation>().get()) {
BRepTools::Clean(shape_);
}
}
void ifcopenshell::geometry::OpenCascadeShape::Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string& r) const {
+18
View File
@@ -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 <size_t N>
struct cartesian_base : public item, public eigen_base<Eigen::Vector3d> {