From 242d70c3116ca7be73607a9adee34c11e6924edb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 26 May 2025 10:47:05 +1000 Subject: [PATCH] Fix set update in loop bug when trying to filter elements by spatial container --- src/bonsai/bonsai/tool/spatial.py | 18 +++++--- src/bonsai/test/bim/feature/spatial.feature | 47 +++++++++++++++++++-- src/bonsai/test/bim/test_feature.py | 22 +++++++++- 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index f58daec3b8..f9fbac77f1 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -281,9 +281,12 @@ class Spatial(bonsai.core.tool.Spatial): if props.should_include_children: elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True) else: - elements = set(ifcopenshell.util.element.get_contained(container)) - for e in elements: - elements.update(ifcopenshell.util.element.get_decomposition(e)) + queue = list(set(ifcopenshell.util.element.get_contained(container))) + elements = set() + while queue: + item = queue.pop() + elements.add(item) + queue.extend(ifcopenshell.util.element.get_decomposition(item)) for element in elements: if element.is_a("IfcOpeningElement") or tool.Root.is_spatial_element(element): continue @@ -1306,9 +1309,12 @@ class Spatial(bonsai.core.tool.Spatial): if props.should_include_children: elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True) else: - elements = set(ifcopenshell.util.element.get_contained(container)) - for e in elements: - elements.update(ifcopenshell.util.element.get_decomposition(e)) + queue = list(set(ifcopenshell.util.element.get_contained(container))) + elements = set() + while queue: + item = queue.pop() + elements.add(item) + queue.extend(ifcopenshell.util.element.get_decomposition(item)) if not element_filter: return elements diff --git a/src/bonsai/test/bim/feature/spatial.feature b/src/bonsai/test/bim/feature/spatial.feature index 43324580e5..de15d6df9b 100644 --- a/src/bonsai/test/bim/feature/spatial.feature +++ b/src/bonsai/test/bim/feature/spatial.feature @@ -2,6 +2,45 @@ Feature: Spatial Covers spatial containment management and spatial tool. +Scenario: Set default container + Given an empty IFC project + When I look at the "Spatial Decomposition" panel + Then I see "My Site" in the "1st" list + And I see "Default: My Storey" + When I select the row where I see "My Building" in the "1st" list + And I click "Set Default" + Then I don't see "Default: My Storey" + And I see "Default: My Building" + +Scenario: Select container + Given an empty IFC project + And I look at the "Spatial Decomposition" panel + When I select the row where I see "My Building" in the "1st" list + And I click "OBJECT_DATA" + Then the object "IfcBuilding/My Building" is selected + +Scenario: View elements - view elements recursively in the project + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcColumnType" + And I add the construction type + When I look at the "Spatial Decomposition" panel + And I select the row where I see "My Project" in the "1st" list + Then I see "IfcColumn" in the "2nd" list + +Scenario: View elements - view elements non-recursively in their container + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcColumnType" + And I add the construction type + When I look at the "Spatial Decomposition" panel + And I select the row where I see "My Project" in the "1st" list + And I click "OUTLINER" + Then there are "1" lists + And I see "No Elements" + And I select the row where I see "My Storey" in the "1st" list + Then I see "IfcColumn" in the "2nd" list + Scenario: Enable editing container Given an empty IFC project And I add a cube @@ -77,7 +116,7 @@ Scenario: Dereference structure And I press "bim.dereference_structure" Then nothing happens -Scenario: Select container +Scenario: Assign container Given an empty IFC project And I add a cube And the object "Cube" is selected @@ -144,7 +183,7 @@ Scenario: Spatial decomposition - see panel Then the "BIM_UL_containers_manager" list has 4 items And I don't see the "BIM_UL_elements" list -Scenario: Isolate spatial container +Scenario: Set element visibility - Isolate spatial container Given an empty IFC project And I trigger "Add Element" And I set the "Definition" property to "IfcElement" @@ -156,7 +195,7 @@ Scenario: Isolate spatial container And I click "FULLSCREEN_EXIT" Then nothing happens -Scenario: Show spatial container +Scenario: Set element visibility - Show spatial container Given an empty IFC project And I trigger "Add Element" And I set the "Definition" property to "IfcElement" @@ -168,7 +207,7 @@ Scenario: Show spatial container And I click "HIDE_OFF" Then nothing happens -Scenario: Hide spatial container +Scenario: Set element visibility - Hide spatial container Given an empty IFC project And I trigger "Add Element" And I set the "Definition" property to "IfcElement" diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py index 137a1f88cd..4a44ad7787 100644 --- a/src/bonsai/test/bim/test_feature.py +++ b/src/bonsai/test/bim/test_feature.py @@ -355,6 +355,16 @@ def i_see_text(text): assert [l for l in panel_spy.spied_labels if text in l], f"Text {text} not found in {panel_spy.spied_labels}" +@given(parsers.parse('there are "{n}" lists')) +@when(parsers.parse('there are "{n}" lists')) +@then(parsers.parse('there are "{n}" lists')) +def there_are_n_lists(n): + assert panel_spy + panel_spy.refresh_spy() + if len(panel_spy.spied_lists) != int(n): + assert False, f"Actual number of lists {len(panel_spy.spied_lists)} not {n}" + + @given(parsers.parse('I see "{text}" in the "{nth}" list')) @when(parsers.parse('I see "{text}" in the "{nth}" list')) @then(parsers.parse('I see "{text}" in the "{nth}" list')) @@ -369,6 +379,10 @@ def i_see_text_in_the_nth_list(text, nth): if i + 1 != nth: continue for row in template_list.rows: + for p in row.spied_props: + debug.append(str(p)) + if isinstance(p["value"], str) and text in p["value"]: + return True for l in row.spied_labels: debug.append(l) if text in l: @@ -446,12 +460,17 @@ def i_select_the_row_where_i_see_text_in_the_nth_list(text, nth): continue for i, row in enumerate(template_list.rows): is_row = False + for p in row.spied_props: + debug.append(str(p)) + if isinstance(p["value"], str) and text in p["value"]: + is_row = True for l in row.spied_labels: debug.append(l) if text in l: is_row = True if is_row: template_list.set_active_index(i) + panel_spy.is_spy_dirty = True return True debug = "\n".join(debug) assert False, f"Could not see '{text}' in any list. We saw:\n{debug}" @@ -745,12 +764,13 @@ def _i_click_button_on_panel(button, panel_spy): if button in (spied_prop["name"], spied_prop["text"], spied_prop["icon"]): val = getattr(spied_prop["props"], spied_prop["name"]) setattr(spied_prop["props"], spied_prop["name"], not bool(val)) + panel_spy.is_spy_dirty = True return if button == "OK" and panel_spy.panel.bl_rna.base.name == "Operator": # Clicked confirm on an operator's draw dialog return i_press_operator(panel_spy.panel.bl_idname) debug = "\n".join([f"{i} {v}" for i, v in enumerate(panel_spy.spied_operators)]) - debug += f"\nHere is the text we see: {panel_spy.spied_labels}" + debug += f"\nHere is the text we see:\n{panel_spy.spied_labels}\n... and props:\n {panel_spy.spied_props}" assert False, f"Could not find {button}:\n{debug}"