mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 18:16:40 +00:00
Show occurrences in spatial manager decomposed elements
Example - https://imgur.com/a/PNFUbv0
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user