From 8b73de5d4222a619750051afa88e5f1e0490378e Mon Sep 17 00:00:00 2001 From: CyrilWaechter Date: Thu, 30 Jul 2026 15:28:23 +0200 Subject: [PATCH] Fix shapely topology crash in boundary generation Add buffer(0) validation for space_face_polygon and face_polygon before intersection, following the same pattern as tool/cad.py. Wrap the intersection in try/except for shapely.errors.GEOSException to catch remaining topology errors. On exception, set bonsai.last_error (so the 'Copy Error Message To Clipboard' button appears in the UI), report an ERROR to the operator, and continue processing other face pairs instead of crashing. Generated with the assistance of an AI coding tool. --- .../bonsai/bim/module/boundary/operator.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/bonsai/bonsai/bim/module/boundary/operator.py b/src/bonsai/bonsai/bim/module/boundary/operator.py index 8a99f0f6c1..86cbe0aa31 100644 --- a/src/bonsai/bonsai/bim/module/boundary/operator.py +++ b/src/bonsai/bonsai/bim/module/boundary/operator.py @@ -18,6 +18,7 @@ import logging import multiprocessing +import traceback from math import acos, degrees, inf, pi, radians from typing import Optional, Union @@ -38,6 +39,7 @@ import shapely.ops from ifcopenshell.util.shape_builder import ShapeBuilder from mathutils import Matrix, Vector +import bonsai import bonsai.bim.import_ifc as import_ifc import bonsai.core.attribute as core import bonsai.core.geometry @@ -804,12 +806,26 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator): space_face_polygon = shapely.Polygon( [tuple((space_face_matrix_i @ v).xy) for v in space_face_verts] ) + if not space_face_polygon.is_valid: + space_face_polygon = space_face_polygon.buffer(0) space_matrix_world_i = space_obj.matrix_world.inverted() face_verts = [space_matrix_world_i @ building_obj.matrix_world @ v.co.copy() for v in face.verts] face_polygon = shapely.Polygon([tuple((space_face_matrix_i @ v).xy) for v in face_verts]) + if not face_polygon.is_valid: + face_polygon = face_polygon.buffer(0) - gross_boundary_polygon = space_face_polygon.intersection(face_polygon) + try: + gross_boundary_polygon = space_face_polygon.intersection(face_polygon) + except shapely.errors.GEOSException: + bonsai.last_error = traceback.format_exc() + element_name = building_element.Name or building_element.is_a() + self.report( + {"ERROR"}, + f"Skipping invalid geometry for {element_name} (shapely topology error). " + "See 'Copy Error Message To Clipboard' for details.", + ) + continue if type(gross_boundary_polygon) == shapely.GeometryCollection: for geom in gross_boundary_polygon.geoms: