Show occurrences in spatial manager decomposed elements

Example - https://imgur.com/a/PNFUbv0
This commit is contained in:
Andrej730
2024-08-27 18:25:17 +05:00
parent fc04c6e41e
commit 760816b7f0
6 changed files with 102 additions and 41 deletions
@@ -247,10 +247,10 @@ class ToggleContainerElement(bpy.types.Operator):
bl_idname = "bim.toggle_container_element" bl_idname = "bim.toggle_container_element"
bl_label = "Toggle Container Element" bl_label = "Toggle Container Element"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty() element_index: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
core.toggle_container_element(tool.Spatial, ifc_class=self.ifc_class) core.toggle_container_element(tool.Spatial, element_index=self.element_index)
return {"FINISHED"} return {"FINISHED"}
@@ -271,21 +271,29 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
return self.execute(context) return self.execute(context)
def _execute(self, context): def _execute(self, context):
ifc_class = relating_type = is_untyped = None ifc_class = relating_type = None
is_untyped = False
ifc_file = tool.Ifc.get()
if self.should_filter: if self.should_filter:
active_element = context.scene.BIMSpatialDecompositionProperties.active_element active_element = context.scene.BIMSpatialDecompositionProperties.active_element
if active_element.is_class: element_type = active_element.type
if element_type == "CLASS":
ifc_class = active_element.name ifc_class = active_element.name
elif relating_type := active_element.ifc_definition_id: elif element_type == "TYPE":
ifc_class = active_element.ifc_class ifc_class = active_element.ifc_class
relating_type = tool.Ifc.get().by_id(relating_type) if ifc_id := active_element.ifc_definition_id:
else: relating_type = ifc_file.by_id(ifc_id)
ifc_class = active_element.ifc_class else: # OCCURRENCE
is_untyped = True occurrence = ifc_file.by_id(active_element.ifc_definition_id)
obj = tool.Ifc.get_object(occurrence)
assert isinstance(obj, bpy.types.Object)
tool.Blender.set_active_object(obj)
return
element_filter = context.scene.BIMSpatialDecompositionProperties.element_filter element_filter = context.scene.BIMSpatialDecompositionProperties.element_filter
core.select_decomposed_elements( core.select_decomposed_elements(
tool.Spatial, tool.Spatial,
container=tool.Ifc.get().by_id(self.container), container=ifc_file.by_id(self.container),
ifc_class=ifc_class, ifc_class=ifc_class,
relating_type=relating_type, relating_type=relating_type,
is_untyped=is_untyped, is_untyped=is_untyped,
+14 -5
View File
@@ -107,12 +107,21 @@ class BIMContainer(PropertyGroup):
class Element(PropertyGroup): class Element(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
ifc_class: StringProperty(name="Name") ifc_class: StringProperty(name="Name", description="Type or element IFC class, empty if 'type' is 'CLASS'")
is_class: BoolProperty(name="Is Class", default=False) ifc_definition_id: IntProperty(
is_type: BoolProperty(name="Is Type", default=False) name="IFC Definition ID",
ifc_definition_id: IntProperty(name="IFC Definition ID") description="ID of the element type / occurrence. 0 if 'type' is 'CLASS' or if it's 'TYPE' but it represents untyped elements",
)
total: IntProperty(name="Total") total: IntProperty(name="Total")
is_expanded: BoolProperty(name="Is Expanded", default=False) is_expanded: BoolProperty(name="Is Expanded", default=False)
type: EnumProperty(
name="Element Type",
items=(
("CLASS", "CLASS", "CLASS"),
("TYPE", "TYPE", "TYPE"),
("OCCURRENCE", "OCCURRENCE", "OCCURRENCE"),
),
)
class BIMSpatialDecompositionProperties(PropertyGroup): class BIMSpatialDecompositionProperties(PropertyGroup):
@@ -122,7 +131,7 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index) active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index)
element_filter: StringProperty(name="Element Filter", default="", options={"TEXTEDIT_UPDATE"}) element_filter: StringProperty(name="Element Filter", default="", options={"TEXTEDIT_UPDATE"})
elements: CollectionProperty(name="Elements", type=Element) elements: CollectionProperty(name="Elements", type=Element)
contracted_classes: StringProperty(name="Contracted Classes", default="[]") expanded_elements: StringProperty(name="Expanded Elements", default="{}")
active_element_index: IntProperty(name="Active Element Index") active_element_index: IntProperty(name="Active Element Index")
total_elements: IntProperty(name="Total Elements") total_elements: IntProperty(name="Total Elements")
subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class") subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class")
+14 -5
View File
@@ -258,22 +258,31 @@ class BIM_UL_elements(UIList):
def __init__(self): def __init__(self):
self.use_filter_show = True self.use_filter_show = True
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_toggle(self, row: bpy.types.UILayout, is_expanded: bool, index: int):
icon_id = "DISCLOSURE_TRI_DOWN" if is_expanded else "DISCLOSURE_TRI_RIGHT"
row.operator("bim.toggle_container_element", text="", emboss=False, icon=icon_id).element_index = index
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, fit_flag):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
if item.is_class: item_type = item.type
icon_id = "DISCLOSURE_TRI_DOWN" if item.is_expanded else "DISCLOSURE_TRI_RIGHT" if item_type == "CLASS":
row.operator("bim.toggle_container_element", text="", emboss=False, icon=icon_id).ifc_class = item.name self.draw_toggle(row, item.is_expanded, index)
row.label(text=item.name) row.label(text=item.name)
col = row.column() col = row.column()
col.alignment = "RIGHT" col.alignment = "RIGHT"
col.label(text=str(item.total)) col.label(text=str(item.total))
elif item.is_type: elif item_type == "TYPE":
row.label(text="", icon="BLANK1") row.label(text="", icon="BLANK1")
self.draw_toggle(row, item.is_expanded, index)
row.label(text=item.name) row.label(text=item.name)
col = row.column() col = row.column()
col.alignment = "RIGHT" col.alignment = "RIGHT"
col.label(text=str(item.total)) col.label(text=str(item.total))
else: # OCCURRENCE
for _ in range(2):
row.label(text="", icon="BLANK1")
row.label(text=item.name)
def draw_filter(self, context, layout): def draw_filter(self, context, layout):
row = layout.row() row = layout.row()
+2 -2
View File
@@ -141,8 +141,8 @@ def delete_container(
spatial.import_spatial_decomposition() spatial.import_spatial_decomposition()
def toggle_container_element(spatial: tool.Spatial, ifc_class: str) -> None: def toggle_container_element(spatial: tool.Spatial, element_index: int) -> None:
spatial.toggle_container_element(ifc_class) spatial.toggle_container_element(element_index)
spatial.load_contained_elements() spatial.load_contained_elements()
+4
View File
@@ -49,6 +49,8 @@ class Covering(bonsai.core.tool.Covering):
def covering_poll_wall_selected( def covering_poll_wall_selected(
cls, operator: type[bpy.types.Operator], context: bpy.types.Context, covering_type: str cls, operator: type[bpy.types.Operator], context: bpy.types.Context, covering_type: str
) -> bool: ) -> bool:
if not tool.Ifc.get():
return False
if not context.selected_objects or not context.active_object: if not context.selected_objects or not context.active_object:
operator.poll_message_set("No objects selected.") operator.poll_message_set("No objects selected.")
return False return False
@@ -62,6 +64,8 @@ class Covering(bonsai.core.tool.Covering):
def covering_poll_relating_type_check( def covering_poll_relating_type_check(
cls, operator: type[bpy.types.Operator], context: bpy.types.Context, covering_type: str cls, operator: type[bpy.types.Operator], context: bpy.types.Context, covering_type: str
) -> bool: ) -> bool:
if not tool.Ifc.get():
return False
props = context.scene.BIMModelProperties props = context.scene.BIMModelProperties
relating_type_id = tool.Blender.get_enum_safe(props, "relating_type_id") relating_type_id = tool.Blender.get_enum_safe(props, "relating_type_id")
if relating_type_id is not None: if relating_type_id is not None:
+50 -19
View File
@@ -39,7 +39,8 @@ import json
from math import pi from math import pi
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from shapely import Polygon from shapely import Polygon
from typing import Generator, Optional, Union, Literal, List from typing import Generator, Optional, Union, Literal, List, Any, Iterable
from collections import defaultdict
class Spatial(bonsai.core.tool.Spatial): class Spatial(bonsai.core.tool.Spatial):
@@ -139,7 +140,7 @@ class Spatial(bonsai.core.tool.Spatial):
target_obj.matrix_world = relative_to_obj.matrix_world @ matrix target_obj.matrix_world = relative_to_obj.matrix_world @ matrix
@classmethod @classmethod
def select_products(cls, products: list[ifcopenshell.entity_instance], unhide: bool = False) -> None: def select_products(cls, products: Iterable[ifcopenshell.entity_instance], unhide: bool = False) -> None:
bpy.ops.object.select_all(action="DESELECT") bpy.ops.object.select_all(action="DESELECT")
for product in products: for product in products:
obj = tool.Ifc.get_object(product) obj = tool.Ifc.get_object(product)
@@ -206,7 +207,7 @@ class Spatial(bonsai.core.tool.Spatial):
container = tool.Ifc.get().by_id(container.ifc_definition_id) container = tool.Ifc.get().by_id(container.ifc_definition_id)
results = {} results: defaultdict[str, dict[int, Any]] = defaultdict(dict)
if props.should_include_children: if props.should_include_children:
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True) elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True)
else: else:
@@ -218,29 +219,47 @@ class Spatial(bonsai.core.tool.Spatial):
ifc_class = element.is_a() ifc_class = element.is_a()
ifc_definition_id = element_type.id() if element_type else 0 ifc_definition_id = element_type.id() if element_type else 0
type_name = element_type.Name or "Unnamed" if element_type else f"Untyped {element.is_a()}" type_name = element_type.Name or "Unnamed" if element_type else f"Untyped {element.is_a()}"
results.setdefault(ifc_class, {}).setdefault(ifc_definition_id, {"total": 0, "type_name": type_name}) class_data = results.setdefault(ifc_class, {})
results[ifc_class][ifc_definition_id]["total"] += 1 type_data = class_data.setdefault(ifc_definition_id, {"type_name": type_name, "elements": []})
type_data["elements"].append(element)
contracted_classes = json.loads(props.contracted_classes) expanded_elements = json.loads(props.expanded_elements)
expanded_classes = expanded_elements.get("CLASS", [])
expanded_ifc_ids = expanded_elements.get("IFC_ID", [])
expanded_untyped = expanded_elements.get("UNTYPED_CLASSES", [])
total_elements = 0 total_elements = 0
for ifc_class in sorted(results.keys()): for ifc_class in sorted(results.keys()):
new = props.elements.add() new = props.elements.add()
new.name = ifc_class new.name = ifc_class
new.is_class = True new.type = "CLASS"
class_is_expanded = ifc_class not in contracted_classes class_is_expanded = ifc_class in expanded_classes
new.is_expanded = class_is_expanded new.is_expanded = class_is_expanded
total = 0 total = 0
for ifc_definition_id in sorted( for ifc_definition_id in sorted(
results[ifc_class].keys(), key=lambda x: results[ifc_class][x]["type_name"] results[ifc_class].keys(), key=lambda x: results[ifc_class][x]["type_name"]
): ):
total2 = results[ifc_class][ifc_definition_id]["total"] type_data = results[ifc_class][ifc_definition_id]
total2 = len(type_data["elements"])
if class_is_expanded: if class_is_expanded:
new2 = props.elements.add() new2 = props.elements.add()
new2.is_type = True new2.type = "TYPE"
new2.name = results[ifc_class][ifc_definition_id]["type_name"] new2.name = type_data["type_name"]
new2.ifc_class = ifc_class new2.ifc_class = ifc_class
new2.total = total2 new2.total = total2
new2.ifc_definition_id = ifc_definition_id new2.ifc_definition_id = ifc_definition_id
if ifc_definition_id == 0:
type_is_expanded = ifc_class in expanded_untyped
else:
type_is_expanded = ifc_definition_id in expanded_ifc_ids
new2.is_expanded = type_is_expanded
if type_is_expanded:
for element in type_data["elements"]:
occurrence = props.elements.add()
occurrence.name = element.Name or "Unnamed"
occurrence.ifc_definition_id = element.id()
occurrence.type = "OCCURRENCE"
total += total2 total += total2
new.total = total new.total = total
total_elements += total total_elements += total
@@ -253,12 +272,12 @@ class Spatial(bonsai.core.tool.Spatial):
elements: List[ifcopenshell.entity_instance], elements: List[ifcopenshell.entity_instance],
ifc_class: str | None, ifc_class: str | None,
relating_type: ifcopenshell.entity_instance | None, relating_type: ifcopenshell.entity_instance | None,
is_untyped: bool | None, is_untyped: bool,
keyword: str | None, keyword: str | None,
) -> filter[ifcopenshell.entity_instance]: ) -> filter[ifcopenshell.entity_instance]:
keyword = keyword.lower() if keyword else keyword keyword = keyword.lower() if keyword else keyword
def filter_element(element): def filter_element(element: ifcopenshell.entity_instance) -> bool:
if ifc_class: if ifc_class:
if not element.is_a(ifc_class): if not element.is_a(ifc_class):
return False return False
@@ -344,14 +363,26 @@ class Spatial(bonsai.core.tool.Spatial):
props.contracted_containers = json.dumps(contracted_containers) props.contracted_containers = json.dumps(contracted_containers)
@classmethod @classmethod
def toggle_container_element(cls, ifc_class: str) -> None: def toggle_container_element(cls, element_index: int) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties props = bpy.context.scene.BIMSpatialDecompositionProperties
contracted_classes: list[str] = json.loads(props.contracted_classes) expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
if ifc_class in contracted_classes: element = props.elements[element_index]
contracted_classes.remove(ifc_class) if element.type == "CLASS":
element_type = "CLASS"
filtered_item = element.name
else: else:
contracted_classes.append(ifc_class) if element.ifc_definition_id == 0:
props.contracted_classes = json.dumps(contracted_classes) element_type = "UNTYPED_CLASSES"
filtered_item = element.ifc_class
else:
element_type = "IFC_ID"
filtered_item = element.ifc_definition_id
expanded_elements_list: list[Union[str, int]] = expanded_elements.setdefault(element_type, [])
if filtered_item in expanded_elements_list:
expanded_elements_list.remove(filtered_item)
else:
expanded_elements_list.append(filtered_item)
props.expanded_elements = json.dumps(expanded_elements)
# HERE STARTS SPATIAL TOOL # HERE STARTS SPATIAL TOOL