Warn when a face inner boundary intersects another boundary (#527)

A face whose inner boundary crosses the outer boundary (or another inner
boundary) is invalid per the schema. Open Cascade silently heals or drops
such a face, so the intended hole is lost or the face is corrupted with no
diagnostic at all (the 2018 report saw a dropped face; on the current line
the face survives as wrong geometry, still silently).

After the wires are collected, if a face has inner boundaries, measure the
BRepExtrema distance between each inner wire and every earlier wire. Two
non intersecting loops have strictly positive distance, so a distance at
or below the modelling precision means the boundaries touch or cross; emit
a warning (GEO 402) naming the offending face. This is diagnostic only, no
geometry change.

The message is emitted via the kernel logger() rather than Logger::Root():
IfcConvert configures a local Logger and worker logs merge into it, while
Logger::Root() is a separate unconfigured singleton whose messages are
discarded (a latent issue affecting some existing GEO messages too).

Verified on OCC 7.9.2 with synthesized IFC4 faces: an inner triangle
crossing the outer edge, and one straddling the bottom edge, each emit one
GEO 402; a valid 4x4 hole emits none and triangulates identically (area
84.0), in both sequential and multithreaded runs. Pure inner self
intersection and full containment are distinct classes and intentionally
left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Petru Conduraru
2026-07-11 09:12:18 +03:00
committed by Thomas Krijnen
parent a8d0ef3437
commit 061bb90d50
+22
View File
@@ -31,6 +31,7 @@
#include <ShapeFix_Shape.hxx>
#include <ShapeFix_ShapeTolerance.hxx>
#include <BRep_Tool.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
#include <Standard_Macro.hxx>
#include <TopoDS_Shape.hxx>
@@ -356,6 +357,27 @@ bool OpenCascadeKernel::convert(const taxonomy::face::ptr face, TopoDS_Shape& re
return false;
}
// #527: A face whose inner boundary intersects the outer boundary (or
// another inner boundary) is invalid per the schema. Open Cascade heals or
// drops such a face silently, so the intended hole is lost with no
// diagnostic. The distance between two non-intersecting loops is strictly
// positive; a distance at (or below) the modelling precision means the
// boundaries touch or cross. Emit a clear warning so the invalid input is
// not silently lost. wires() is ordered outer-first, inner-bounds after.
if (fd.wires().size() > 1) {
const auto& fwires = fd.wires();
bool reported = false;
for (size_t i = 1; i < fwires.size() && !reported; ++i) {
for (size_t j = 0; j < i && !reported; ++j) {
BRepExtrema_DistShapeShape dss(fwires[i], fwires[j]);
if (dss.IsDone() && dss.Value() < precision_) {
logger().Warning("GEO", 402, "Face inner boundary intersects another face boundary", face->instance);
reported = true;
}
}
}
}
if (fd.surface().IsNull()) {
// Use the first wire to find a plane manually for polygonal wires
const TopoDS_Wire& wire = fd.wires().front();