diff --git a/src/ifcgeom/kernels/opencascade/boolean_utils.cpp b/src/ifcgeom/kernels/opencascade/boolean_utils.cpp index d104c7ff94..08e379561c 100644 --- a/src/ifcgeom/kernels/opencascade/boolean_utils.cpp +++ b/src/ifcgeom/kernels/opencascade/boolean_utils.cpp @@ -832,9 +832,18 @@ bool IfcGeom::util::points_on_planar_face_generator::operator()(gp_Pnt& p) { } -bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const TopoDS_Shape& a_input, const NCollection_List& b_input, BOPAlgo_Operation op, TopoDS_Shape& result, double fuzziness) { +bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const TopoDS_Shape& a_input, const NCollection_List& b_input, BOPAlgo_Operation op, TopoDS_Shape& result, double fuzziness, bool allow_nonmanifold_fallback) { using namespace std::string_literals; + // A geometrically valid (BRepCheck_Analyzer) subtraction result that is + // rejected only for being non-manifold is remembered here. Some real models + // (e.g. a mitered/half-space beam end cut that grazes fine end detailing, + // see #1015) can only be resolved into such a result. Silently discarding + // the whole cut and returning the un-subtracted first operand is worse than + // keeping this result, so it is used as a last resort once the fuzziness + // retries are exhausted. + TopoDS_Shape nonmanifold_fallback; + const bool do_unify = true; const bool do_subtraction_eliminate_disjoint_bbox = true; const bool do_subtraction_eliminate_touching = true; @@ -1390,6 +1399,12 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To } else { settings.log().Notice("GEO", 151, "Boolean operation yields non-manifold result"); + // r passed BRepCheck_Analyzer above and was rejected solely for + // being non-manifold. Remember it as a last-resort fallback so a + // difficult but valid subtraction is not dropped entirely (#1015). + if (op == BOPAlgo_CUT && nonmanifold_fallback.IsNull() && !r.IsNull()) { + nonmanifold_fallback = r; + } } } } @@ -1417,10 +1432,25 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To } if (!success) { if (allow_retry) { - return boolean_operation(settings, a, b, op, result, new_fuzziness); + // Retry at a higher fuzziness in the hope of a clean manifold result. + // The retry is not allowed to apply the non-manifold fallback itself: + // that decision is deferred to this (finer-fuzziness) frame so that, + // if every retry also fails, the most accurate fallback is used. + if (boolean_operation(settings, a, b, op, result, new_fuzziness, false)) { + return true; + } } else { settings.log().Notice("GEO", 154, "No longer attempting boolean operation with higher fuzziness"); } + + if (allow_nonmanifold_fallback && !nonmanifold_fallback.IsNull()) { + // Every attempt failed the manifold check but produced a geometrically + // valid subtraction. Keeping it preserves the boolean cut instead of + // silently returning the un-subtracted first operand (#1015). + settings.log().Message(Logger::LOG_WARNING, "GEO", 155, "Boolean subtraction result is non-manifold, using it regardless to preserve the cut"); + result = nonmanifold_fallback; + return true; + } } return success && !result.IsNull(); } diff --git a/src/ifcgeom/kernels/opencascade/boolean_utils.h b/src/ifcgeom/kernels/opencascade/boolean_utils.h index 55bd960130..cd848c949d 100644 --- a/src/ifcgeom/kernels/opencascade/boolean_utils.h +++ b/src/ifcgeom/kernels/opencascade/boolean_utils.h @@ -104,7 +104,7 @@ namespace IfcGeom { Logger& log() const { return logger ? *logger : Logger::Root(); } }; - bool boolean_operation(const boolean_settings& settings, const TopoDS_Shape&, const NCollection_List&, BOPAlgo_Operation, TopoDS_Shape&, double fuzziness = -1.); + bool boolean_operation(const boolean_settings& settings, const TopoDS_Shape&, const NCollection_List&, BOPAlgo_Operation, TopoDS_Shape&, double fuzziness = -1., bool allow_nonmanifold_fallback = true); bool boolean_operation(const boolean_settings& settings, const TopoDS_Shape&, const TopoDS_Shape&, BOPAlgo_Operation, TopoDS_Shape&, double fuzziness = -1.);