From 1a849395c26e64d86933afc92b0590b2b9d1f0b1 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 26 Mar 2026 12:43:47 +1100 Subject: [PATCH] Typo crashing edit tools panel when non-wall with wall selected Fix #7034 bpy.ops.bim.extend_to_underside doesn't exist - the correct operator name is bim.extend_walls_to_underside. The AttributeError killed the entire panel draw, hiding mirror, align, aggregation, and QTO buttons. Co-Authored-By: Claude Opus 4.6 --- .../bonsai/bim/module/model/workspace.py | 2 +- src/bonsai/test/bim/feature/model.feature | 38 +++++++++++++++++++ src/bonsai/test/bim/test_feature.py | 32 +++++++++++++++- 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index 5f7cf7699d..828fc2c21f 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -943,7 +943,7 @@ class EditObjectUI: if "LAYER2" in AuthoringData.data["selected_material_usages"]: row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row add_layout_hotkey_operator( - cls.layout, "Extend To Underside", "S_E", bpy.ops.bim.extend_to_underside.__doc__, ui_context + cls.layout, "Extend To Underside", "S_E", bpy.ops.bim.extend_walls_to_underside.__doc__, ui_context ) if AuthoringData.data["is_flippable_element"]: diff --git a/src/bonsai/test/bim/feature/model.feature b/src/bonsai/test/bim/feature/model.feature index ad5b25bd07..064620a574 100644 --- a/src/bonsai/test/bim/feature/model.feature +++ b/src/bonsai/test/bim/feature/model.feature @@ -396,6 +396,44 @@ Scenario: Add a slab And the object "IfcSlab/Slab" bottom left corner is at "0,0,0" And the object "IfcSlab/Slab" top right corner is at "1,1,0.2" +Scenario: Extend walls to underside + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_occurrence" + And I set "scene.BIMModelProperties.ifc_class" to "IfcSlabType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR200'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_occurrence" + And the object "IfcSlab/Slab" is moved to "0,0,2.5" + When the object "IfcWall/Wall" is selected + And additionally the object "IfcSlab/Slab" is selected + And I look at the tool header + And I click "Extend To Underside" + Then the object "IfcWall/Wall" dimensions are "1,0.1,2.5" + +Scenario: Extend walls to underside - extending to a tessellated gable roof + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_occurrence" + # Create gable roof: a cube turned into a prism with a ridge. + And I add a cube of size "1" at "0.5,0.05,3" + And the object "Cube" is selected + And I evaluate expression "obj = bpy.context.active_object; [setattr(v.co, 'y', 0) for v in obj.data.vertices if v.co.z > 0]" + And I set "scene.BIMRootProperties.ifc_product" to "IfcElement" + And I set "scene.BIMRootProperties.ifc_class" to "IfcRoof" + And I press "bim.assign_class" + When the object "IfcWall/Wall" is selected + And additionally the object "IfcRoof/Cube" is selected + And I look at the tool header + And I click "Extend To Underside" + Then the object "IfcWall/Wall" dimensions are "1,0.1,2.5" + Scenario: Enable editing a slab profile Given an empty IFC project And I load the demo construction library diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py index b7dc932024..79e8494441 100644 --- a/src/bonsai/test/bim/test_feature.py +++ b/src/bonsai/test/bim/test_feature.py @@ -133,7 +133,11 @@ class PanelSpy: self.spied_labels.append(kwargs["text"]) return self elif self.spied_attr == "prop": - props, name = args + if args: + props, name = args + else: + props = kwargs.get("data") + name = kwargs.get("property") props: bpy.types.bpy_struct text = kwargs.get("text", props.bl_rna.properties[name].name) icon = kwargs.get("icon", None) @@ -390,6 +394,32 @@ def i_look_at_the_panel_panel(panel: str) -> None: panel_spy.refresh_spy() +@given(parsers.parse("I look at the tool header")) +@when(parsers.parse("I look at the tool header")) +@then(parsers.parse("I look at the tool header")) +def i_look_at_the_tool_header() -> None: + from bonsai.bim.module.model.workspace import EditObjectUI + + class MockRegion: + type = "UI" + + class MockContext: + def __getattr__(self, name): + if name == "region": + return MockRegion() + return getattr(bpy.context, name) + + global panel_spy + panel_spy = PanelSpy(EditObjectUI) + panel_spy.is_spy_dirty = False + panel_spy.spied_attr = None + panel_spy.spied_labels = [] + panel_spy.spied_props = [] + panel_spy.spied_operators = [] + panel_spy.spied_lists = [] + EditObjectUI.draw(MockContext(), panel_spy) + + @given(parsers.parse('I open the "{name}" menu')) @when(parsers.parse('I open the "{name}" menu')) @then(parsers.parse('I open the "{name}" menu'))