From bb908f3dfa969d9d3ebc75f7a3b8928ddea75a77 Mon Sep 17 00:00:00 2001 From: Stephen Boddy Date: Sat, 11 Jul 2026 19:05:56 +0100 Subject: [PATCH] Route boolean-op kernel logging through the injected logger Ports #96e2efebc onto wgpu. wgpu's boolean_utils logged via the global ::logger::root() singleton (which IfcConvert never wires to --log-file), so boolean-op messages were effectively dropped. Thread the caller's injected logger through instead: - boolean_settings gains `::logger* logger` + `log()` accessor (falls back to ::logger::root()); boolean_operation logs via settings.log() - eliminate_narrow_operands / boolean_subtraction_2d_using_builder take a `::logger& logger = ::logger::root()` param; boolean_operation passes settings.log() into them - OpenCascadeKernel / boolean_result set bst.logger = &logger_ and log via logger_ (were ::logger::root()) Adapted from v0.8.0's Logger/Logger::Root() to wgpu's ::logger/::logger::root(). Co-Authored-By: Claude Opus 4.8 --- .../kernels/opencascade/OpenCascadeKernel.cpp | 1 + .../kernels/opencascade/boolean_result.cpp | 7 +- .../kernels/opencascade/boolean_utils.cpp | 76 +++++++++---------- .../kernels/opencascade/boolean_utils.h | 9 ++- 4 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.cpp b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.cpp index 054e4dcfa5..75651b5c99 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.cpp +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.cpp @@ -50,6 +50,7 @@ bool IfcGeom::OpenCascadeKernel::convert_openings(const express::Base& entity, c bst.attempt_2d = settings_.get().get(); bst.debug = settings_.get().get(); bst.precision = settings_.get().get(); + bst.logger = &logger_; std::vector< std::pair > opening_vector; diff --git a/src/ifcgeom/kernels/opencascade/boolean_result.cpp b/src/ifcgeom/kernels/opencascade/boolean_result.cpp index fd396a3731..d4305181e6 100644 --- a/src/ifcgeom/kernels/opencascade/boolean_result.cpp +++ b/src/ifcgeom/kernels/opencascade/boolean_result.cpp @@ -118,14 +118,14 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::boolean_result::ptr br, Con const double first_operand_volume = util::shape_volume(a); if (first_operand_volume <= ALMOST_ZERO) { - ::logger::root().message(::logger::LOG_WARNING, "GEO", 119, "Empty solid for:", c->instance); + logger_.message(::logger::LOG_WARNING, "GEO", 119, "Empty solid for:", c->instance); } } else { for (auto& r : cr) { auto S = std::static_pointer_cast(r.Shape())->shape(); if (S.IsNull()) { - ::logger::root().error("GEO", 120, "Null operand"); + logger_.error("GEO", 120, "Null operand"); continue; } gp_GTrsf trsf; @@ -140,7 +140,7 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::boolean_result::ptr br, Con // #2665 we also set a precision-independent threshold, because in the boolean op routine // the working fuzziness might still be increased. if (d < tol * 20. || d < 0.00002) { - ::logger::root().message(::logger::LOG_WARNING, "GEO", 121, "Halfspace subtraction yields unchanged volume:", c->instance); + logger_.message(::logger::LOG_WARNING, "GEO", 121, "Halfspace subtraction yields unchanged volume:", c->instance); continue; } else { S = result; @@ -159,6 +159,7 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::boolean_result::ptr br, Con bst.attempt_2d = settings_.get().get(); bst.debug = settings_.get().get(); bst.precision = settings_.get().get(); + bst.logger = &logger_; TopoDS_Shape r; diff --git a/src/ifcgeom/kernels/opencascade/boolean_utils.cpp b/src/ifcgeom/kernels/opencascade/boolean_utils.cpp index 0cf1920607..67eb7a9fc8 100644 --- a/src/ifcgeom/kernels/opencascade/boolean_utils.cpp +++ b/src/ifcgeom/kernels/opencascade/boolean_utils.cpp @@ -405,7 +405,7 @@ bool IfcGeom::util::is_extrusion(const gp_Vec & v, const TopoDS_Shape & s, TopoD return true; } -int IfcGeom::util::eliminate_narrow_operands(double prec, const NCollection_List& bs, NCollection_List & c) { +int IfcGeom::util::eliminate_narrow_operands(double prec, const NCollection_List& bs, NCollection_List & c, ::logger& logger) { int N = 0; NCollection_List::Iterator it(bs); for (; it.More(); it.Next()) { @@ -418,7 +418,7 @@ int IfcGeom::util::eliminate_narrow_operands(double prec, const NCollection_List bool is_narrow = min_dimension < prec; - ::logger::root().notice("GEO", 122, "Min OBB dimension of operand = " + std::to_string(min_dimension)); + logger.notice("GEO", 122, "Min OBB dimension of operand = " + std::to_string(min_dimension)); if (!is_narrow) { c.Append(it.Value()); @@ -573,7 +573,7 @@ int IfcGeom::util::eliminate_touching_operands(double prec, const TopoDS_Shape & return N; } -bool IfcGeom::util::boolean_subtraction_2d_using_builder(const TopoDS_Shape & a_input, const NCollection_List & b_input, TopoDS_Shape & result, double eps) { +bool IfcGeom::util::boolean_subtraction_2d_using_builder(const TopoDS_Shape & a_input, const NCollection_List & b_input, TopoDS_Shape & result, double eps, ::logger& logger) { IfcGeom::impl::tree edge_tree; NCollection_List ab_input = b_input; @@ -703,7 +703,7 @@ bool IfcGeom::util::boolean_subtraction_2d_using_builder(const TopoDS_Shape & a_ if (u11 < U1 && U1 < u12 && u21 < U2 && U2 < u22) { // Edge curves belonging to different operands intersect, don't process // using builder. - ::logger::root().notice("GEO", 123, "Intersecting boundaries"); + logger.notice("GEO", 123, "Intersecting boundaries"); return false; } } @@ -750,7 +750,7 @@ bool IfcGeom::util::boolean_subtraction_2d_using_builder(const TopoDS_Shape & a_ // any effect and marked as redundant. Feeding it to the builder algo // will likely cause problems. redundant[std::distance(wires.begin(), it)] = true; - ::logger::root().notice("GEO", 124, "Subtraction operand outside of outer bound"); + logger.notice("GEO", 124, "Subtraction operand outside of outer bound"); } } @@ -790,7 +790,7 @@ bool IfcGeom::util::boolean_subtraction_2d_using_builder(const TopoDS_Shape & a_ if (wire_clss[wire_index]->Perform(p2d) == TopAbs_IN) { // A wire is contained within another operand redundant[other_index] = true; - ::logger::root().notice("GEO", 125, "Subtraction operand contained in other"); + logger.notice("GEO", 125, "Subtraction operand contained in other"); } } } @@ -848,7 +848,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To std::stringstream ss; ss << "bool-" << std::this_thread::get_id() << "-" << (operation_counter_++); debug_identifier = ss.str(); - ::logger::root().notice("GEO", 126, "Boolean debug identifier: " + debug_identifier); + settings.log().notice("GEO", 126, "Boolean debug identifier: " + debug_identifier); } if (fuzziness < 0.) { @@ -884,7 +884,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To a = unify(a_input, fuzziness * 1000.); - ::logger::root().message( + settings.log().message( ::logger::LOG_DEBUG, "GEO", 127, "Simplified operand A from "s + std::to_string(count(a_input, TopAbs_FACE)) + @@ -896,7 +896,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To NCollection_List::Iterator it(b_input); for (; it.More(); it.Next()) { b.Append(unify(it.Value(), fuzziness)); - ::logger::root().message( + settings.log().message( ::logger::LOG_DEBUG, "GEO", 128, "Simplified operand B from "s + std::to_string(count(it.Value(), TopAbs_FACE)) + @@ -924,7 +924,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To auto N = bounding_box_overlap(fuzziness, a, b, b_tmp); if (N) { - ::logger::root().notice("GEO", 129, "Eliminated " + std::to_string(N) + " disjoint operands"); + settings.log().notice("GEO", 129, "Eliminated " + std::to_string(N) + " disjoint operands"); std::swap(b, b_tmp); } } @@ -935,7 +935,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To b_tmp.Clear(); auto N = eliminate_touching_operands(fuzziness, a, b, b_tmp); if (N) { - ::logger::root().notice("GEO", 130, "Eliminated " + std::to_string(N) + " touching operands"); + settings.log().notice("GEO", 130, "Eliminated " + std::to_string(N) + " touching operands"); std::swap(b, b_tmp); } } @@ -944,9 +944,9 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To PERF("boolean subtraction: eliminate narrow"); b_tmp.Clear(); - auto N = eliminate_narrow_operands(fuzziness, b, b_tmp); + auto N = eliminate_narrow_operands(fuzziness, b, b_tmp, settings.log()); if (N) { - ::logger::root().notice("GEO", 131, "Eliminated " + std::to_string(N) + " narrow operands"); + settings.log().notice("GEO", 131, "Eliminated " + std::to_string(N) + " narrow operands"); std::swap(b, b_tmp); } } @@ -960,21 +960,21 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To } if (b.Extent() == 0) { - ::logger::root().warning("GEO", 132, "No other operands remaining, using first operand"); + settings.log().warning("GEO", 132, "No other operands remaining, using first operand"); result = a; return true; } - if (!is_2d && ::logger::LOG_NOTICE >= ::logger::root().verbosity()) { + if (!is_2d && ::logger::LOG_NOTICE >= settings.log().verbosity()) { PERF("preliminary manifoldness check"); if (!a.IsNull()) { - ::logger::root().notice("GEO", 133, "Operand A is " + (is_manifold(a) ? ""s : "non-"s) + "manifold"); + settings.log().notice("GEO", 133, "Operand A is " + (is_manifold(a) ? ""s : "non-"s) + "manifold"); } NCollection_List::Iterator it(b); for (int i = 0; it.More(); it.Next(), ++i) { - ::logger::root().notice("GEO", 134, "Operand B " + std::to_string(i) + " is " + (is_manifold(it.Value()) ? ""s : "non-"s) + "manifold"); + settings.log().notice("GEO", 134, "Operand B " + std::to_string(i) + " is " + (is_manifold(it.Value()) ? ""s : "non-"s) + "manifold"); } } @@ -1014,7 +1014,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To const double fuzz = (std::min)(min_length_orig / 3., fuzziness); - ::logger::root().notice("GEO", 135, "Used fuzziness: " + std::to_string(fuzz)); + settings.log().notice("GEO", 135, "Used fuzziness: " + std::to_string(fuzz)); const double new_fuzziness = fuzziness * 10.; const bool allow_retry = new_fuzziness - 1e-15 <= settings.precision * 10000. && new_fuzziness < min_length_orig; @@ -1048,7 +1048,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To } if (is_extrusion_a) { - ::logger::root().notice("GEO", 136, "Operand A 1/1 is an extrusion"); + settings.log().notice("GEO", 136, "Operand A 1/1 is an extrusion"); NCollection_List::Iterator it(b); for (int nb = 1; it.More(); it.Next(), ++nb) { @@ -1064,10 +1064,10 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To } if (is_extrusion_b) { - ::logger::root().notice("GEO", 137, "Operand B " + std::to_string(nb) + "/" + std::to_string(b.Extent()) + " is an extrusion"); + settings.log().notice("GEO", 137, "Operand B " + std::to_string(nb) + "/" + std::to_string(b.Extent()) + " is an extrusion"); if (b_interval.first < a_interval.first + (fuzz * 100.) && b_interval.second > a_interval.second - (fuzz * 100.)) { - ::logger::root().notice("GEO", 138, "Operand B creates a through hole"); + settings.log().notice("GEO", 138, "Operand B creates a through hole"); // Align b with a operand gp_Trsf trsf; @@ -1091,7 +1091,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To PERF("boolean operation: 2d builder"); // First try using face builder - boolean_op_2d_success = boolean_subtraction_2d_using_builder(a_face, b_faces, face_result, fuzziness); + boolean_op_2d_success = boolean_subtraction_2d_using_builder(a_face, b_faces, face_result, fuzziness, settings.log()); } if (!boolean_op_2d_success) { @@ -1107,23 +1107,23 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To BRepPrimAPI_MakePrism mp(face_result, gp_Vec(gp::DY()) * (a_interval.second - a_interval.first)); if (mp.IsDone()) { if (b_remainder_3d.Extent()) { - ::logger::root().notice("GEO", 139, std::to_string(b_remainder_3d.Extent()) + " operands remaining to process in 3D"); + settings.log().notice("GEO", 139, std::to_string(b_remainder_3d.Extent()) + " operands remaining to process in 3D"); b = b_remainder_3d; s1s.Clear(); s1s.Append(mp.Shape()); } else { - ::logger::root().notice("GEO", 140, "Processed fully in 2D"); + settings.log().notice("GEO", 140, "Processed fully in 2D"); result = mp.Shape(); return true; } } else { - ::logger::root().notice("GEO", 141, "Failed to extrude 2D boolean result. Retrying in 3D."); + settings.log().notice("GEO", 141, "Failed to extrude 2D boolean result. Retrying in 3D."); } } else { - ::logger::root().notice("GEO", 142, "Failed to perform 2D boolean operation. Retrying in 3D."); + settings.log().notice("GEO", 142, "Failed to perform 2D boolean operation. Retrying in 3D."); } } else { - ::logger::root().notice("GEO", 143, "No second operands can be processed as 2D inner bounds. Retrying in 3D."); + settings.log().notice("GEO", 143, "No second operands can be processed as 2D inner bounds. Retrying in 3D."); } } } @@ -1145,7 +1145,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To } if (builder->IsDone()) { if (false && builder->DSFiller()->HasWarning(STANDARD_TYPE(BOPAlgo_AlertAcquiredSelfIntersection))) { - ::logger::root().notice("GEO", 144, "Builder reports self-intersection in output"); + settings.log().notice("GEO", 144, "Builder reports self-intersection in output"); success = false; /* @@ -1159,7 +1159,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To } */ } else if(builder->DSFiller()->HasWarning(STANDARD_TYPE(BOPAlgo_AlertBadPositioning)) && !TopoDS_Iterator(*builder).More()) { - ::logger::root().notice("GEO", 145, "Builder reports bad positioning and result is empty"); + settings.log().notice("GEO", 145, "Builder reports bad positioning and result is empty"); success = false; } else { TopoDS_Shape r = *builder; @@ -1173,7 +1173,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To fix.Perform(); r = fix.Shape(); } catch (...) { - ::logger::root().error("GEO", 146, "Shape healing failed on boolean result"); + settings.log().error("GEO", 146, "Shape healing failed on boolean result"); } } @@ -1184,7 +1184,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To success = ana.IsValid() != 0; if (!success) { - ::logger::root().notice("GEO", 147, "Boolean operation yields invalid result"); + settings.log().notice("GEO", 147, "Boolean operation yields invalid result"); std::stringstream str; bool any_emitted = false; @@ -1214,7 +1214,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To dump(r); - ::logger::root().notice("GEO", 148, str.str()); + settings.log().notice("GEO", 148, str.str()); } } @@ -1334,7 +1334,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To if (op == BOPAlgo_CUT && has_open_shells && all_faces_included_in_result && result_n_faces > first_op_n_faces) { success = false; - ::logger::root().notice("GEO", 149, "Boolean result discarded because subtractions results in only the addition of faces"); + settings.log().notice("GEO", 149, "Boolean result discarded because subtractions results in only the addition of faces"); } else { // when there are edges or vertex-edge distances close to the used fuzziness, the // output is not trusted and the operation is attempted with a higher fuzziness. @@ -1380,7 +1380,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To static const char* const reason_strings[] = { "edge length", "vertex-edge", "face-face" }; std::stringstream str; str << "Boolean operation result failing " << reason_strings[reason] << " interference check, with fuzziness " << fuzziness << " with length " << v; - ::logger::root().notice("GEO", 150, str.str()); + settings.log().notice("GEO", 150, str.str()); } } @@ -1389,7 +1389,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To } } else { - ::logger::root().notice("GEO", 151, "Boolean operation yields non-manifold result"); + settings.log().notice("GEO", 151, "Boolean operation yields non-manifold result"); } } } @@ -1399,7 +1399,7 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To #if OCC_VERSION_HEX >= 0x70200 if (builder->HasError(STANDARD_TYPE(BOPAlgo_AlertBOPNotAllowed))) { - ::logger::root().error("GEO", 152, "Invalid operands. Using first operand"); + settings.log().error("GEO", 152, "Invalid operands. Using first operand"); result = a; success = true; } @@ -1412,14 +1412,14 @@ bool IfcGeom::util::boolean_operation(const boolean_settings& settings, const To #endif std::string str_str = str.str(); if (str_str.size()) { - ::logger::root().notice("GEO", 153, str_str); + settings.log().notice("GEO", 153, str_str); } } if (!success) { if (allow_retry) { return boolean_operation(settings, a, b, op, result, new_fuzziness); } else { - ::logger::root().notice("GEO", 154, "No longer attempting boolean operation with higher fuzziness"); + settings.log().notice("GEO", 154, "No longer attempting boolean operation with higher fuzziness"); } } return success && !result.IsNull(); diff --git a/src/ifcgeom/kernels/opencascade/boolean_utils.h b/src/ifcgeom/kernels/opencascade/boolean_utils.h index 3b49e0fc51..ca217d8a7f 100644 --- a/src/ifcgeom/kernels/opencascade/boolean_utils.h +++ b/src/ifcgeom/kernels/opencascade/boolean_utils.h @@ -35,6 +35,7 @@ #include +#include "../../../ifcparse/logger.h" #include "../ifc_geomlibrary_api.h" namespace IfcGeom { @@ -88,13 +89,17 @@ namespace IfcGeom { IFC_GEOMLIBRARY_API int eliminate_touching_operands(double prec, const TopoDS_Shape& a, const NCollection_List& bs, NCollection_List& c); - IFC_GEOMLIBRARY_API int eliminate_narrow_operands(double prec, const NCollection_List& bs, NCollection_List & c); + IFC_GEOMLIBRARY_API int eliminate_narrow_operands(double prec, const NCollection_List& bs, NCollection_List & c, ::logger& logger = ::logger::root()); - IFC_GEOMLIBRARY_API bool boolean_subtraction_2d_using_builder(const TopoDS_Shape& a_input, const NCollection_List& b_input, TopoDS_Shape& result, double eps); + IFC_GEOMLIBRARY_API bool boolean_subtraction_2d_using_builder(const TopoDS_Shape& a_input, const NCollection_List& b_input, TopoDS_Shape& result, double eps, ::logger& logger = ::logger::root()); struct boolean_settings { bool debug, attempt_2d; double precision; + // Set by callers that carry a per-conversion logger (e.g. kernels deriving + // from AbstractKernel). Falls back to the global ::logger::root() singleton. + ::logger* logger = nullptr; + ::logger& log() const { return logger ? *logger : ::logger::root(); } }; IFC_GEOMLIBRARY_API bool boolean_operation(const boolean_settings& settings, const TopoDS_Shape&, const NCollection_List&, BOPAlgo_Operation, TopoDS_Shape&, double fuzziness = -1.);