Compare commits

...

2 Commits

Author SHA1 Message Date
Petru Conduraru 69479155fd Bonsai: fix UnboundLocalError when a None-geometry item is a SweptAreaSolid
The previous commit guarded import_item against tool.Loader.create_generic_shape
returning None, but only assigned cartesian_point_offset inside the walrus
operator of the non-None branch. When geometry is None and the item is also an
IfcSweptAreaSolid, the code after the if/else unconditionally reads
cartesian_point_offset to adjust item_matrix, raising an UnboundLocalError
instead of the intended graceful error log.

Move the cartesian_point_offset lookup out of the walrus operator so it is
always computed, matching its use later in the function regardless of which
branch ran.

Verified live in headless Blender: reproduced both the original AttributeError
('NoneType' has no attribute 'verts_buffer', matching the issue's traceback)
and this UnboundLocalError with a synthetic IfcExtrudedAreaSolid item whose
create_generic_shape was made to return None (the reporter's real .ifc file
was shared privately with maintainers and was not available to us); confirmed
the fixed code path completes without crashing and logs the error instead.

Fixes #6693.

Generated with the assistance of an AI coding tool.
2026-07-16 15:12:36 +03:00
Petru Conduraru 00e67d7c68 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.
2026-07-16 15:00:55 +03:00
+26 -8
View File
@@ -2229,17 +2229,35 @@ class Geometry(bonsai.core.tool.Geometry):
co = np.array(item.VertexGeometry.Coordinates) * ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
obj.data.from_pydata([co], [], [])
else:
cartesian_point_offset = cls.get_cartesian_point_offset(rep_obj)
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 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