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"