From 00e67d7c68de9aa9b8feefa466dbcf3db92ce438 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Thu, 16 Jul 2026 15:00:55 +0300 Subject: [PATCH] Bonsai: guard import_item against a None geometry shape Editing an IfcRepresentationItem and exiting item edit mode re-imports its geometry through tool.Geometry.import_item, which calls tool.Loader.create_generic_shape(item) and immediately passes the result to ifcopenshell.util.shape.get_vertices without checking it. The geometry kernel can legitimately return None for an item, for example when a boolean or clipping result is invalid or has no remaining volume, which is exactly what every other caller of create_generic_shape already guards against (import_ifc.py and tool/loader.py both check the result before using it). Without the same guard here, get_vertices(None) raises an unhandled AttributeError partway through import_item, after obj.data.clear_geometry() has already run, leaving the item's mesh cleared and the operator aborted instead of failing gracefully. This adds the same None check used elsewhere in the codebase: on failure we log a clear error naming the offending item instead of crashing, and skip only the geometry-dependent steps. Fixes #6693. Generated with the assistance of an AI coding tool. --- src/bonsai/bonsai/tool/geometry.py | 33 ++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index 08abd38b22..cae18e2139 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -2230,16 +2230,33 @@ class Geometry(bonsai.core.tool.Geometry): obj.data.from_pydata([co], [], []) else: geometry = tool.Loader.create_generic_shape(item) - verts = ifcopenshell.util.shape.get_vertices(geometry) - if (cartesian_point_offset := cls.get_cartesian_point_offset(rep_obj)) is not None: - verts = verts - cartesian_point_offset - tool.Loader.convert_geometry_to_mesh(geometry, obj.data, verts=verts) + if geometry is None: + # The geometry kernel can legitimately fail to produce a shape for an + # item (e.g. an invalid or degenerate boolean/clipping result). Every + # other caller of create_generic_shape already guards against this + # (see import_ifc.py and tool/loader.py). Without the same guard here, + # get_vertices(None) raises an unhandled AttributeError which aborts + # mid-edit after obj.data.clear_geometry() already ran, leaving the + # item's mesh empty/corrupted instead of failing gracefully. + logging.getLogger("ImportIFC").error( + "Failed to regenerate geometry for representation item #%d (%s). " + "Its mesh has been cleared - check for invalid geometry, such as " + "a boolean or clipping result with no remaining volume.", + item.id(), + item.is_a(), + ) + obj.matrix_world = rep_obj.matrix_world.copy() + else: + verts = ifcopenshell.util.shape.get_vertices(geometry) + if (cartesian_point_offset := cls.get_cartesian_point_offset(rep_obj)) is not None: + verts = verts - cartesian_point_offset + tool.Loader.convert_geometry_to_mesh(geometry, obj.data, verts=verts) - if ios_materials := list(obj.data["ios_materials"]): - material = tool.Ifc.get_object(tool.Ifc.get().by_id(ios_materials[0])) - obj.data.materials.append(material) + if ios_materials := list(obj.data["ios_materials"]): + material = tool.Ifc.get_object(tool.Ifc.get().by_id(ios_materials[0])) + obj.data.materials.append(material) - obj.matrix_world = rep_obj.matrix_world.copy() + obj.matrix_world = rep_obj.matrix_world.copy() if is_swept_area := item.is_a("IfcSweptAreaSolid"): position = item.Position