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_label = "Toggle Container Element"
bl_options = {"REGISTER", "UNDO"}
ifc_class: bpy.props.StringProperty()
element_index: bpy.props.IntProperty()
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"}
@@ -271,21 +271,29 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
return self.execute(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:
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
elif relating_type := active_element.ifc_definition_id:
elif element_type == "TYPE":
ifc_class = active_element.ifc_class
relating_type = tool.Ifc.get().by_id(relating_type)
else:
ifc_class = active_element.ifc_class
is_untyped = True
if ifc_id := active_element.ifc_definition_id:
relating_type = ifc_file.by_id(ifc_id)
else: # OCCURRENCE
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
core.select_decomposed_elements(
tool.Spatial,
container=tool.Ifc.get().by_id(self.container),
container=ifc_file.by_id(self.container),
ifc_class=ifc_class,
relating_type=relating_type,
is_untyped=is_untyped,
+14 -5
View File
@@ -107,12 +107,21 @@ class BIMContainer(PropertyGroup):
class Element(PropertyGroup):
name: StringProperty(name="Name")
ifc_class: StringProperty(name="Name")
is_class: BoolProperty(name="Is Class", default=False)
is_type: BoolProperty(name="Is Type", default=False)
ifc_definition_id: IntProperty(name="IFC Definition ID")
ifc_class: StringProperty(name="Name", description="Type or element IFC class, empty if 'type' is 'CLASS'")
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")
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):
@@ -122,7 +131,7 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index)
element_filter: StringProperty(name="Element Filter", default="", options={"TEXTEDIT_UPDATE"})
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")
total_elements: IntProperty(name="Total Elements")
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):
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:
row = layout.row(align=True)
if item.is_class:
icon_id = "DISCLOSURE_TRI_DOWN" if item.is_expanded else "DISCLOSURE_TRI_RIGHT"
row.operator("bim.toggle_container_element", text="", emboss=False, icon=icon_id).ifc_class = item.name
item_type = item.type
if item_type == "CLASS":
self.draw_toggle(row, item.is_expanded, index)
row.label(text=item.name)
col = row.column()
col.alignment = "RIGHT"
col.label(text=str(item.total))
elif item.is_type:
elif item_type == "TYPE":
row.label(text="", icon="BLANK1")
self.draw_toggle(row, item.is_expanded, index)
row.label(text=item.name)
col = row.column()
col.alignment = "RIGHT"
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):
row = layout.row()
+2 -2
View File
@@ -141,8 +141,8 @@ def delete_container(
spatial.import_spatial_decomposition()
def toggle_container_element(spatial: tool.Spatial, ifc_class: str) -> None:
spatial.toggle_container_element(ifc_class)
def toggle_container_element(spatial: tool.Spatial, element_index: int) -> None:
spatial.toggle_container_element(element_index)
spatial.load_contained_elements()
+4
View File
@@ -49,6 +49,8 @@ class Covering(bonsai.core.tool.Covering):
def covering_poll_wall_selected(
cls, operator: type[bpy.types.Operator], context: bpy.types.Context, covering_type: str
) -> bool:
if not tool.Ifc.get():
return False
if not context.selected_objects or not context.active_object:
operator.poll_message_set("No objects selected.")
return False
@@ -62,6 +64,8 @@ class Covering(bonsai.core.tool.Covering):
def covering_poll_relating_type_check(
cls, operator: type[bpy.types.Operator], context: bpy.types.Context, covering_type: str
) -> bool:
if not tool.Ifc.get():
return False
props = context.scene.BIMModelProperties
relating_type_id = tool.Blender.get_enum_safe(props, "relating_type_id")
if relating_type_id is not None:
+50 -19
View File
@@ -39,7 +39,8 @@ import json
from math import pi
from mathutils import Vector, Matrix
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):
@@ -139,7 +140,7 @@ class Spatial(bonsai.core.tool.Spatial):
target_obj.matrix_world = relative_to_obj.matrix_world @ matrix
@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")
for product in products:
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)
results = {}
results: defaultdict[str, dict[int, Any]] = defaultdict(dict)
if props.should_include_children:
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True)
else:
@@ -218,29 +219,47 @@ class Spatial(bonsai.core.tool.Spatial):
ifc_class = element.is_a()
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()}"
results.setdefault(ifc_class, {}).setdefault(ifc_definition_id, {"total": 0, "type_name": type_name})
results[ifc_class][ifc_definition_id]["total"] += 1
class_data = results.setdefault(ifc_class, {})
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
for ifc_class in sorted(results.keys()):
new = props.elements.add()
new.name = ifc_class
new.is_class = True
class_is_expanded = ifc_class not in contracted_classes
new.type = "CLASS"
class_is_expanded = ifc_class in expanded_classes
new.is_expanded = class_is_expanded
total = 0
for ifc_definition_id in sorted(
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:
new2 = props.elements.add()
new2.is_type = True
new2.name = results[ifc_class][ifc_definition_id]["type_name"]
new2.type = "TYPE"
new2.name = type_data["type_name"]
new2.ifc_class = ifc_class
new2.total = total2
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
new.total = total
total_elements += total
@@ -253,12 +272,12 @@ class Spatial(bonsai.core.tool.Spatial):
elements: List[ifcopenshell.entity_instance],
ifc_class: str | None,
relating_type: ifcopenshell.entity_instance | None,
is_untyped: bool | None,
is_untyped: bool,
keyword: str | None,
) -> filter[ifcopenshell.entity_instance]:
keyword = keyword.lower() if keyword else keyword
def filter_element(element):
def filter_element(element: ifcopenshell.entity_instance) -> bool:
if ifc_class:
if not element.is_a(ifc_class):
return False
@@ -344,14 +363,26 @@ class Spatial(bonsai.core.tool.Spatial):
props.contracted_containers = json.dumps(contracted_containers)
@classmethod
def toggle_container_element(cls, ifc_class: str) -> None:
def toggle_container_element(cls, element_index: int) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
contracted_classes: list[str] = json.loads(props.contracted_classes)
if ifc_class in contracted_classes:
contracted_classes.remove(ifc_class)
expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
element = props.elements[element_index]
if element.type == "CLASS":
element_type = "CLASS"
filtered_item = element.name
else:
contracted_classes.append(ifc_class)
props.contracted_classes = json.dumps(contracted_classes)
if element.ifc_definition_id == 0:
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