diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 137bed3371..0028548e76 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -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, diff --git a/src/bonsai/test/bim/module/model/test_regenerate_wall.py b/src/bonsai/test/bim/module/model/test_regenerate_wall.py index 68ca4bcdf9..feaf858186 100644 --- a/src/bonsai/test/bim/module/model/test_regenerate_wall.py +++ b/src/bonsai/test/bim/module/model/test_regenerate_wall.py @@ -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() diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py index e59c6e1efa..080d7da99f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py @@ -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)