From 4004344c20069bb2e9ae1db8e94dd9badb979dcb Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Mon, 29 Jun 2026 09:26:19 +0200 Subject: [PATCH] Bonsai: TAB enters wall parametric edit Mirror the icon-click entry into wall parametric edit on the TAB key. The dispatch in Modifier.try_applying_edit_mode had no branch for fresh LAYER2 walls, so TAB landed in item mode instead of the parametric draft + gizmos. Add the missing entry leg of the toggle, placed after the generic is_object_editing branch so the finish leg still fires when a wall is already in edit mode. Generated with the assistance of an AI coding tool. --- src/bonsai/bonsai/tool/blender.py | 4 + .../test_wall_tab_enters_parametric_edit.py | 98 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/bonsai/test/bim/module/model/test_wall_tab_enters_parametric_edit.py diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 0594958613..bd5a45f9b8 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1524,6 +1524,10 @@ class Blender(bonsai.core.tool.Blender): bpy.ops.bim.enable_editing_railing_path() elif feature := tool.Parametric.is_object_editing(obj): tool.Parametric.run_bim_op(feature.finish_op) + elif tool.Parametric.is_wall(element): + # Placed after the generic finish dispatch so the TAB toggle splits: + # wall already editing → finish above; wall not editing → enter here. + bpy.ops.bim.enable_editing_wall() else: return False return True diff --git a/src/bonsai/test/bim/module/model/test_wall_tab_enters_parametric_edit.py b/src/bonsai/test/bim/module/model/test_wall_tab_enters_parametric_edit.py new file mode 100644 index 0000000000..f434c67c49 --- /dev/null +++ b/src/bonsai/test/bim/module/model/test_wall_tab_enters_parametric_edit.py @@ -0,0 +1,98 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2026 +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Bonsai. If not, see . +# +# This file was generated with the assistance of an AI coding tool. + +"""Pins TAB-key dispatch on a LAYER2 wall through ``Modifier.try_applying_edit_mode``. + +Three behavioural legs of the TAB toggle: + +* Fresh LAYER2 wall → enters parametric edit via ``bim.enable_editing_wall``. +* LAYER2 wall already in parametric edit → finishes (toggle close). +* Wall without ``IfcMaterialLayerSetUsage`` → dispatch returns False so the + caller routes the TAB to item mode.""" + +import bpy +import ifcopenshell.api.material +import ifcopenshell.util.element +import pytest + +import bonsai.tool as tool +from test.bim.bootstrap import NewFile + +pytestmark = pytest.mark.model + + +def _add_layer2_wall_occurrence(): + """Create a single LAYER2 wall occurrence from the IFC4 Demo Template. + + Returns the (element, obj) pair, with the object selected and active so + ``try_applying_edit_mode`` reads the same context the TAB-key operator + chain would feed it.""" + tool.Project.get_project_props().template_file = "IFC4 Demo Template.ifc" + bpy.ops.bim.create_project() + ifc_file = tool.Ifc.get() + wall_type = next(t for t in ifc_file.by_type("IfcWallType") if tool.Model.get_usage_type(t) == "LAYER2") + bpy.ops.bim.add_occurrence(relating_type_id=wall_type.id()) + wall = ifc_file.by_type("IfcWall")[0] + obj = tool.Ifc.get_object(wall) + assert isinstance(obj, bpy.types.Object) + tool.Blender.set_objects_selection(bpy.context, obj, (obj,)) + return wall, obj + + +class TestTabOnLayer2WallEntersParametricEdit(NewFile): + def test_dispatch_enables_wall_edit(self): + wall, obj = _add_layer2_wall_occurrence() + assert tool.Parametric.is_wall(wall) is True + assert obj.BIMWallProperties.is_editing is False + + result = tool.Blender.Modifier.try_applying_edit_mode(obj, wall) + + assert result is True + assert obj.BIMWallProperties.is_editing is True + + +class TestTabOnLayer2WallAlreadyEditingFinishes(NewFile): + def test_dispatch_finishes_wall_edit(self): + wall, obj = _add_layer2_wall_occurrence() + bpy.ops.bim.enable_editing_wall() + assert obj.BIMWallProperties.is_editing is True + + result = tool.Blender.Modifier.try_applying_edit_mode(obj, wall) + + assert result is True + assert obj.BIMWallProperties.is_editing is False + + +class TestTabOnNonLayer2WallReturnsFalse(NewFile): + def test_dispatch_falls_through_for_wall_without_layer_set_usage(self): + """A wall without ``IfcMaterialLayerSetUsage`` is not a parametric-edit + target; the dispatch returns False so the caller routes the TAB to + item mode.""" + wall, obj = _add_layer2_wall_occurrence() + ifc_file = tool.Ifc.get() + wall_type = ifcopenshell.util.element.get_type(wall) + ifcopenshell.api.material.unassign_material(ifc_file, products=[wall, wall_type]) + assert tool.Parametric.is_wall(wall) is False + assert obj.BIMWallProperties.is_editing is False + + result = tool.Blender.Modifier.try_applying_edit_mode(obj, wall) + + assert result is False + assert obj.BIMWallProperties.is_editing is False