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 <noreply@anthropic.com>
This commit is contained in:
Stephen Boddy
2026-08-27 21:56:22 +01:00
parent 05ba93ab61
commit e5cf83df6e
3 changed files with 51 additions and 1 deletions
+12 -1
View File
@@ -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) {
+36
View File
@@ -25,6 +25,7 @@
#include <string>
#include <fstream>
#include <iostream>
#include <cstdio>
#include <limits>
#include <algorithm>
@@ -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<std::string>(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<std::string>(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<std::string>(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<std::string> object_type;
if (!brep_obj->product().get("ObjectType").isNull()) {
+3
View File
@@ -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<std::vector<double>> dash_array=std::nullopt, std::optional<std::string> 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);