mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-24 10:46:55 +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_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,
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user