Bonsai: short-circuit recreate_wall when no layer set

regenerate_wall_representation returns None for walls without an
IfcMaterialLayerSet (the only mode it knows how to rebuild). Feeding
None to switch_representation crashes deep in resolve_representation
on .Items. Document the None return on the API side and bail in
tool.Model.recreate_wall when it hits.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Gorgious56
2026-06-24 09:16:04 +02:00
parent 10ee5aef4f
commit 3b584ef242
3 changed files with 30 additions and 2 deletions
+3
View File
@@ -3058,6 +3058,9 @@ class Model(bonsai.core.tool.Model):
regenerate_fillet_corner_wall(element, obj)
return
rep = ifcopenshell.api.geometry.regenerate_wall_representation(tool.Ifc.get(), element)
if rep is None:
# Wall has no IfcMaterialLayerSet — layer-set rebuild not applicable.
return
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
@@ -83,3 +83,25 @@ def test_regenerate_wall_noops_when_obj_has_no_ifc_entity():
recreate.assert_not_called()
has_top.assert_not_called()
regen.assert_not_called()
def test_recreate_wall_noops_when_wall_has_no_layer_set():
"""``recreate_wall`` must short-circuit when
``regenerate_wall_representation`` returns ``None``. That API only knows
how to rebuild ``IfcMaterialLayerSet`` walls; for walls without one it
returns ``None``, and feeding ``None`` to ``switch_representation``
crashes deep inside ``resolve_representation`` on ``.Items``."""
element = Mock()
obj = Mock()
with patch("bonsai.tool.model.tool.Parametric.is_fillet_corner_wall", return_value=False), patch(
"bonsai.tool.model.tool.Ifc.get", return_value=Mock()
), patch("bonsai.tool.model.ifcopenshell.api.geometry.regenerate_wall_representation", return_value=None), patch(
"bonsai.tool.model.bonsai.core.geometry.switch_representation"
) as switch, patch.object(
tool.Geometry, "record_object_materials"
) as record:
tool.Model.recreate_wall(element, obj)
switch.assert_not_called()
record.assert_not_called()
@@ -44,7 +44,7 @@ def regenerate_wall_representation(
length: float = 1.0,
height: float = 1.0,
angle: Optional[float] = None,
) -> ifcopenshell.entity_instance:
) -> Optional[ifcopenshell.entity_instance]:
"""
Regenerate the body representation of a wall taking into account connections.
@@ -94,7 +94,10 @@ def regenerate_wall_representation(
default height in SI units.
:param angle: If the wall doesn't already have a slope, this is the default
angle in radians. Left as none or 0 defines no slope.
:return: The newly generated body IfcShapeRepresentation
:return: The newly generated body IfcShapeRepresentation, or ``None`` if
the wall has no ``IfcMaterialLayerSet`` (the layer-set rebuild is the
only mode this function knows; without layers there is nothing to
regenerate and callers should leave the existing representation alone).
"""
return Regenerator(file).regenerate(wall, length=length, height=height, angle=angle)