From e5cf83df6e37f5866d07df8a407dbcbf79310e9f Mon Sep 17 00:00:00 2001 From: Stephen Boddy Date: Thu, 27 Aug 2026 21:56:22 +0100 Subject: [PATCH] svg_serializer: skip elements that fail geometry conversion A single element's geometry can fail deep inside OCCT (e.g. a degenerate surface) without that being representative of the rest of the file. Previously this raised all the way through SWIG as an unhelpful "An unknown error occurred", aborting the whole drawing. Catch the failure per-element, log which element and why (including a temporary stderr mirror, since nothing currently wires up ifcopenshell::logger's output for this code path), and skip just that element so the rest of the drawing still gets produced. Also harden convert_wire_to_faces()'s face-area heuristic in wire_utils.cpp against the same class of rare OCCT failure - area is only used there to drop slivers from self-intersection splitting, so fall back to 0 rather than losing the whole face over it. Generated with the assistance of an AI coding tool. Co-Authored-By: Claude Sonnet 5 --- .../kernels/opencascade/wire_utils.cpp | 13 ++++++- src/serializers/svg_serializer.cpp | 36 +++++++++++++++++++ src/serializers/svg_serializer.h | 3 ++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/ifcgeom/kernels/opencascade/wire_utils.cpp b/src/ifcgeom/kernels/opencascade/wire_utils.cpp index 4ee78a0006..53386d47bc 100644 --- a/src/ifcgeom/kernels/opencascade/wire_utils.cpp +++ b/src/ifcgeom/kernels/opencascade/wire_utils.cpp @@ -937,7 +937,18 @@ bool ifcopenshell::geom::util::convert_wire_to_faces(const TopoDS_Wire& w, TopoD } TopoDS_Face face = mf.Face(); - const double m = face_area(face); + double m; + try { + m = face_area(face); + } catch (const Standard_Failure& e) { + // Area here is only a heuristic to drop slivers from self-intersection splitting; + // don't let a rare OCCT failure computing it (e.g. on a degenerate surface) abort + // conversion of the whole element. Fall back to 0 rather than losing the face. + ifcopenshell::logger::root().warning("GEO", 232, + std::string("Failed to compute face area, assuming 0: ") + + (e.GetMessageString() ? e.GetMessageString() : e.DynamicType()->Name())); + m = 0.; + } face_list.push_back({ m, face }); if (m > max_area) { diff --git a/src/serializers/svg_serializer.cpp b/src/serializers/svg_serializer.cpp index 4027df83aa..e60689a729 100644 --- a/src/serializers/svg_serializer.cpp +++ b/src/serializers/svg_serializer.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -717,6 +718,41 @@ namespace { } void svg_serializer::write(const ifcopenshell::geom::native_element* brep_obj) { + // Geometry conversion for a single element can fail deep inside OCCT (e.g. a degenerate + // surface) without that being representative of the file as a whole. Catch such failures + // here, log which element was responsible, and skip just that element so the rest of the + // drawing still gets produced. + // @todo TEMPORARY: also mirror to stderr, since nothing in Bonsai currently wires up + // logger()'s output for this code path (no set_output()/get_log() call), so a warning() + // here is otherwise invisible. Remove the std::cerr lines once that's addressed upstream. + try { + write_(brep_obj); + } catch (const Standard_Failure& e) { + std::string msg = "SVG serializer OCC exception while writing element #" + + boost::lexical_cast(brep_obj->id()) + " (" + brep_obj->guid() + "): " + + (e.GetMessageString() ? e.GetMessageString() : e.DynamicType()->Name()); + logger().warning("SER", 32, msg); + std::cerr << "[SVG-SKIP] " << msg + << (elevation_ref_guid_ && *elevation_ref_guid_ == brep_obj->guid() ? " [THIS IS THE elevation-ref-guid CAMERA ELEMENT]" : "") + << std::endl; + } catch (const std::exception& e) { + std::string msg = "SVG serializer exception while writing element #" + + boost::lexical_cast(brep_obj->id()) + " (" + brep_obj->guid() + "): " + e.what(); + logger().warning("SER", 33, msg); + std::cerr << "[SVG-SKIP] " << msg + << (elevation_ref_guid_ && *elevation_ref_guid_ == brep_obj->guid() ? " [THIS IS THE elevation-ref-guid CAMERA ELEMENT]" : "") + << std::endl; + } catch (...) { + std::string msg = "SVG serializer encountered an unrecognized exception while writing element #" + + boost::lexical_cast(brep_obj->id()) + " (" + brep_obj->guid() + ")"; + logger().warning("SER", 34, msg); + std::cerr << "[SVG-SKIP] " << msg + << (elevation_ref_guid_ && *elevation_ref_guid_ == brep_obj->guid() ? " [THIS IS THE elevation-ref-guid CAMERA ELEMENT]" : "") + << std::endl; + } +} + +void svg_serializer::write_(const ifcopenshell::geom::native_element* brep_obj) { std::optional object_type; if (!brep_obj->product().get("ObjectType").isNull()) { diff --git a/src/serializers/svg_serializer.h b/src/serializers/svg_serializer.h index fdd12de1a6..8fe2fb568c 100644 --- a/src/serializers/svg_serializer.h +++ b/src/serializers/svg_serializer.h @@ -677,6 +677,9 @@ public: bool ready(); void write(const ifcopenshell::geom::triangulation_element* /*o*/) {} void write(const ifcopenshell::geom::native_element* o); +private: + void write_(const ifcopenshell::geom::native_element* o); +public: void write(path_object& p, const TopoDS_Shape& wire, std::optional> dash_array=std::nullopt, std::optional css_class=std::nullopt); void write(const geometry_data& data); path_object& start_path(const gp_Pln& p, const express::base& storey, const std::string& id);