ifcgeom: keep one copy of repeated faces in faceset helper duplicate removal

When an IfcConnectedFaceSet lists the same IfcFace instance multiple
times, the faceset helper registered the loop identity in duplicates_
and wires() then skipped every occurrence of that identity, so the face
vanished from the shell entirely instead of being deduplicated to a
single copy. The resulting shell had holes, sewing produced a
non-manifold first/second boolean operand, and opening subtraction was
discarded (Boolean operation yields non-manifold result), leaving the
element with no geometry.

Count how many occurrences of a loop identity are redundant and skip
only that many in wires(), so exactly one copy of each repeated face is
still built.

In 418--walls--segfault.ifc the opening of wall #2543 repeats 84 of the
350 unique faces of its closed shell (once to three times over the 518
entries), which previously dropped those 84 faces and yielded a 0-vertex
wall. With this change the opening operand is manifold and the wall gets
a closed, dimensionally correct body. No output changes on the other 257
files in test/input.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Petru Conduraru
2026-07-19 21:35:49 +03:00
parent 89523999b3
commit cad1eecb69
2 changed files with 7 additions and 1 deletions
@@ -86,6 +86,7 @@ private:
private:
OpenCascadeKernel* kernel_;
std::set<int> duplicates_;
std::map<int, int> duplicate_skips_remaining_;
std::map<int, int> vertex_mapping_;
std::map<std::pair<int, int>, TopoDS_Edge> edges_;
double eps_;
@@ -108,6 +108,7 @@ IfcGeom::OpenCascadeKernel::faceset_helper::faceset_helper(
vertex_mapping_.clear();
duplicates_.clear();
duplicate_skips_remaining_.clear();
edge_use.clear();
@@ -174,6 +175,7 @@ IfcGeom::OpenCascadeKernel::faceset_helper::faceset_helper(
if (edge_sets.find({loop->external.get_value_or(false), segment_set}) != edge_sets.end()) {
duplicate_faces++;
duplicates_.insert(loop->identity());
duplicate_skips_remaining_[loop->identity()]++;
continue;
}
edge_sets.insert({loop->external.get_value_or(false), segment_set});
@@ -250,7 +252,10 @@ bool IfcGeom::OpenCascadeKernel::faceset_helper::wire(const ifcopenshell::geomet
}
bool IfcGeom::OpenCascadeKernel::faceset_helper::wires(const ifcopenshell::geometry::taxonomy::loop::ptr loop, NCollection_List<TopoDS_Shape>& wires) {
if (duplicates_.find(loop->identity()) != duplicates_.end()) {
// Skip only the redundant occurrences, keep one copy of the face.
auto it = duplicate_skips_remaining_.find(loop->identity());
if (it != duplicate_skips_remaining_.end() && it->second > 0) {
--it->second;
return false;
}
TopoDS_Wire wire;