From a383f6091520181b15800ee213da55a8103986f1 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 16 Jan 2025 16:40:25 +0500 Subject: [PATCH] Show voided elements in UI E.g. show voided wall when window or wall's opening is selected. Example - https://i.imgur.com/jxjWPeE.png --- src/bonsai/bonsai/bim/module/void/data.py | 53 +++++++++++++++++++++-- src/bonsai/bonsai/bim/module/void/ui.py | 53 ++++++++++++++++++----- 2 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/void/data.py b/src/bonsai/bonsai/bim/module/void/data.py index 4bbb3da1d3..f3d60ec439 100644 --- a/src/bonsai/bonsai/bim/module/void/data.py +++ b/src/bonsai/bonsai/bim/module/void/data.py @@ -17,7 +17,9 @@ # along with Bonsai. If not, see . import bpy +import ifcopenshell import bonsai.tool as tool +from typing import Any, Generator, Union def refresh(): @@ -31,13 +33,25 @@ class VoidsData: @classmethod def load(cls): - cls.data = {"active_opening": cls.active_opening(), "openings": cls.openings(), "fillings": cls.fillings()} + cls.data = { + "active_opening": cls.active_opening(), + "openings": cls.openings(), + "fillings": cls.fillings(), + "voided_element": cls.voided_element(), + "filled_voids": cls.filled_voids(), + } cls.is_loaded = True + @classmethod + def get_element_data(cls, element: ifcopenshell.entity_instance) -> dict[str, Any]: + return {"id": element.id(), "Name": element.Name or "Unnamed"} + @classmethod def active_opening(cls): element = tool.Ifc.get_entity(bpy.context.active_object) + cls.element = element if element and element.is_a("IfcOpeningElement"): + cls.element = element return element.id() @classmethod @@ -51,8 +65,14 @@ class VoidsData: opening = rel.RelatedOpeningElement for rel2 in getattr(opening, "HasFillings", []) or []: filling = rel2.RelatedBuildingElement - has_fillings.append({"id": filling.id(), "Name": filling.Name or "Unnamed"}) - results.append({"id": opening.id(), "Name": opening.Name or "Unnamed", "HasFillings": has_fillings}) + has_fillings.append(cls.get_element_data(filling)) + + results.append( + cls.get_element_data(opening) + | { + "HasFillings": has_fillings, + } + ) return results @classmethod @@ -63,9 +83,34 @@ class VoidsData: results = [] for rel in getattr(element, "HasFillings", []) or []: filling = rel.RelatedBuildingElement - results.append({"id": filling.id(), "Name": filling.Name or "Unnamed"}) + results.append(cls.get_element_data(filling)) return results + @classmethod + def get_voided_element_data(cls, opening: ifcopenshell.entity_instance) -> Union[dict[str, Any], None]: + voids_elements = None + if voids := opening.VoidsElements: + voided_element = voids[0].RelatingBuildingElement + voids_elements = cls.get_element_data(voided_element) + return voids_elements + + @classmethod + def voided_element(cls) -> Union[dict[str, Any], None]: + if not (element := cls.element) or not element.is_a("IfcOpeningElement"): + return None + return cls.get_voided_element_data(element) + + @classmethod + def filled_voids(cls) -> Union[dict[str, Any], None]: + if not (element := cls.element) or not (fills_voids := getattr(element, "FillsVoids", [])): + return None + + opening = fills_voids[0].RelatingOpeningElement + result = cls.get_element_data(opening) | { + "VoidsElements": cls.get_voided_element_data(opening), + } + return result + class BooleansData: data = {} diff --git a/src/bonsai/bonsai/bim/module/void/ui.py b/src/bonsai/bonsai/bim/module/void/ui.py index d2f264f5b2..175884b297 100644 --- a/src/bonsai/bonsai/bim/module/void/ui.py +++ b/src/bonsai/bonsai/bim/module/void/ui.py @@ -16,10 +16,20 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations import bpy import bonsai.tool as tool from bpy.types import Panel, UIList from bonsai.bim.module.void.data import BooleansData, VoidsData +from typing import Any, TYPE_CHECKING + +if TYPE_CHECKING: + import bpy._typing.rna_enums as rna_enums + + +OPENING_ICON = "SELECT_SUBTRACT" +FILLING_ICON = "SELECT_INTERSECT" +VOIDED_ELEMENT_ICON = "SELECT_EXTEND" class BIM_PT_voids(Panel): @@ -55,34 +65,53 @@ class BIM_PT_voids(Panel): if not VoidsData.data["fillings"]: row = self.layout.row() - row.label(text="No Fillings", icon="SELECT_INTERSECT") + row.label(text="No Fillings", icon=FILLING_ICON) for filling in VoidsData.data["fillings"]: row = self.layout.row(align=True) - row.label(text=filling["Name"], icon="SELECT_INTERSECT") - op = row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF") - op.ifc_id = filling["id"] - op.tooltip = "Select filling object." + self.draw_selectable_element_ui(row, filling, "filling", FILLING_ICON) row.operator("bim.remove_filling", icon="X", text="").filling = filling["id"] + + if voided := VoidsData.data["voided_element"]: + row = self.layout.row(align=True) + self.draw_selectable_element_ui(row, voided, "voided", VOIDED_ELEMENT_ICON) + else: if not VoidsData.data["openings"]: row = self.layout.row() - row.label(text="No Openings", icon="SELECT_SUBTRACT") + row.label(text="No Openings", icon=OPENING_ICON) for opening in VoidsData.data["openings"]: if opening["HasFillings"]: for filling in opening["HasFillings"]: row = self.layout.row(align=True) - row.label(text=opening["Name"], icon="SELECT_SUBTRACT") - row.label(text=filling["Name"], icon="SELECT_INTERSECT") - op = row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF") - op.ifc_id = filling["id"] - op.tooltip = "Select filling object." + row.label(text=opening["Name"], icon=OPENING_ICON) + self.draw_selectable_element_ui(row, filling, "filling", FILLING_ICON) else: row = self.layout.row(align=True) - row.label(text=opening["Name"], icon="SELECT_SUBTRACT") + row.label(text=opening["Name"], icon=OPENING_ICON) row.operator("bim.remove_opening", icon="X", text="").opening_id = opening["id"] + if (opening := VoidsData.data["filled_voids"]) is None: + row = self.layout.row() + row.label(text="No Voids Filled", icon=VOIDED_ELEMENT_ICON) + else: + row = self.layout.row() + row.label(text="Voided Element:", icon=VOIDED_ELEMENT_ICON) + row = self.layout.row(align=True) + row.label(text=opening["Name"], icon=OPENING_ICON) + voided_element = opening["VoidsElements"] + if voided_element is not None: + self.draw_selectable_element_ui(row, voided_element, "voided", VOIDED_ELEMENT_ICON) + + def draw_selectable_element_ui( + self, layout: bpy.types.UILayout, element_data: dict[str, Any], object_hint: str, icon: rna_enums.IconItems + ) -> None: + layout.label(text=element_data["Name"], icon=icon) + op = layout.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF") + op.ifc_id = element_data["id"] + op.tooltip = f"Select {object_hint} object." + class BIM_PT_booleans(Panel): bl_label = "Booleans"