Fix #5230. Reimplement spatial decomposition hierarchy.

This commit is contained in:
Dion Moult
2024-09-03 14:36:01 +10:00
parent 4ce30b3153
commit 2ed2b4fe71
3 changed files with 112 additions and 8 deletions
@@ -64,6 +64,10 @@ def update_should_include_children(self, context):
tool.Spatial.load_contained_elements()
def update_element_mode(self, context):
tool.Spatial.load_contained_elements()
def update_container_obj(self, context):
if self.container_obj is None or not (obj := context.active_object):
return
@@ -112,6 +116,8 @@ class Element(PropertyGroup):
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",
)
level: IntProperty(name="Level", default=0)
has_children: BoolProperty(name="Has Children", default=False)
total: IntProperty(name="Total")
is_expanded: BoolProperty(name="Is Expanded", default=False)
type: EnumProperty(
@@ -134,6 +140,14 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
expanded_elements: StringProperty(name="Expanded Elements", default="{}")
active_element_index: IntProperty(name="Active Element Index")
total_elements: IntProperty(name="Total Elements")
element_mode: EnumProperty(
name="Element Mode",
items=(
("TYPE", "Class > Type > Occurrence", "Show a breakdown by IfcClass, relating type, and occurrence"),
("DECOMPOSITION", "Element Decomposition", "Show a breakdown by element decomposition"),
),
update=update_element_mode,
)
subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class")
default_container: IntProperty(name="Default Container", default=0)
should_include_children: BoolProperty(
+17 -8
View File
@@ -160,8 +160,11 @@ class BIM_PT_spatial_decomposition(Panel):
if not self.props.total_elements:
row = self.layout.row(align=True)
row.label(text=f"{self.props.active_container.ifc_class} > No Elements", icon="FILE_3D")
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
row.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").container = ifc_definition_id
row = self.layout.row(align=True)
row.prop(self.props, "element_mode", text="", icon="FILEBROWSER")
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
return
row = self.layout.row(align=True)
@@ -169,7 +172,6 @@ class BIM_PT_spatial_decomposition(Panel):
text=f"{self.props.active_container.ifc_class} > {self.props.total_elements} Elements",
icon="FILE_3D",
)
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
row.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").container = ifc_definition_id
op = row.operator("bim.select_decomposed_element", icon="OBJECT_DATA", text="")
if active_element := self.props.active_element:
@@ -179,6 +181,10 @@ class BIM_PT_spatial_decomposition(Panel):
op = row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="")
op.container = ifc_definition_id
row = self.layout.row(align=True)
row.prop(self.props, "element_mode", text="", icon="FILEBROWSER")
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
self.layout.template_list(
"BIM_UL_elements",
"",
@@ -273,23 +279,26 @@ class BIM_UL_elements(UIList):
if item:
row = layout.row(align=True)
item_type = item.type
if item_type == "CLASS":
for _ in range(item.level):
row.label(text="", icon="BLANK1")
if item.has_children:
self.draw_toggle(row, item.is_expanded, index)
if item_type == "CLASS":
row.label(text=item.name)
col = row.column()
col.alignment = "RIGHT"
col.label(text=str(item.total))
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")
elif item_type == "OCCURRENCE":
row.label(text=item.name)
if item.total:
col = row.column()
col.alignment = "RIGHT"
col.label(text=str(item.total))
def draw_filter(self, context, layout):
row = layout.row()
+81
View File
@@ -226,6 +226,14 @@ class Spatial(bonsai.core.tool.Spatial):
return
container = tool.Ifc.get().by_id(container.ifc_definition_id)
if props.element_mode == "TYPE":
cls.load_contained_elements_by_type(container)
elif props.element_mode == "DECOMPOSITION":
cls.load_contained_elements_by_decomposition(container)
@classmethod
def load_contained_elements_by_type(cls, container: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
results = cls.get_grouped_elements_in_container(container)
expanded_elements = json.loads(props.expanded_elements)
@@ -239,6 +247,7 @@ class Spatial(bonsai.core.tool.Spatial):
new = props.elements.add()
new.name = ifc_class
new.type = "CLASS"
new.has_children = True
class_is_expanded = ifc_class in expanded_classes
class_is_expanded_r = ifc_class in expanded_classes_r
new.is_expanded = class_is_expanded or class_is_expanded_r
@@ -251,6 +260,8 @@ class Spatial(bonsai.core.tool.Spatial):
if new.is_expanded:
new2 = props.elements.add()
new2.type = "TYPE"
new2.has_children = True
new2.level = 1
new2.name = type_data["type_name"]
new2.ifc_class = ifc_class
new2.total = total2
@@ -265,6 +276,7 @@ class Spatial(bonsai.core.tool.Spatial):
for element in type_data["elements"]:
occurrence = props.elements.add()
occurrence.name = element.Name or "Unnamed"
occurrence.level = 2
occurrence.ifc_definition_id = element.id()
occurrence.type = "OCCURRENCE"
@@ -274,6 +286,35 @@ class Spatial(bonsai.core.tool.Spatial):
props.total_elements = total_elements
@classmethod
def load_contained_elements_by_decomposition(cls, container: ifcopenshell.entity_instance) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
expanded_elements = json.loads(props.expanded_elements)
expanded_ifc_ids = expanded_elements.get("IFC_ID", [])
def add_elements(elements, level=0):
for element in sorted(elements, key=lambda x: f"{x.is_a()}/{x.Name or 'Unnamed'}"):
ifc_definition_id = element.id()
name = f"{element.is_a()}/{element.Name or 'Unnamed'}"
new = props.elements.add()
new.name = name
new.level = level
new.ifc_definition_id = ifc_definition_id
new.type = "OCCURRENCE"
children = [
e
for e in ifcopenshell.util.element.get_decomposition(element, is_recursive=False)
if not e.is_a("IfcFeatureElement")
]
if children:
new.has_children = True
new.total = len(children)
if ifc_definition_id in expanded_ifc_ids:
add_elements(children, level=level + 1)
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=False)
add_elements(elements)
@classmethod
def filter_elements(
cls,
@@ -373,7 +414,14 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod
def toggle_container_element(cls, element_index: int, is_recursive: bool) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
if props.element_mode == "TYPE":
cls.toggle_container_element_by_type(element_index, is_recursive)
elif props.element_mode == "DECOMPOSITION":
cls.toggle_container_element_by_decomposition(element_index, is_recursive)
@classmethod
def toggle_container_element_by_type(cls, element_index: int, is_recursive: bool) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
element = props.elements[element_index]
@@ -420,6 +468,39 @@ class Spatial(bonsai.core.tool.Spatial):
props.expanded_elements = json.dumps(expanded_elements)
@classmethod
def toggle_container_element_by_decomposition(cls, element_index: int, is_recursive: bool) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties
element = props.elements[element_index]
expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
expanded_elements_list: list[Union[str, int]] = expanded_elements.setdefault("IFC_ID", [])
ifc_definition_id = element.ifc_definition_id
if ifc_definition_id in expanded_elements_list:
expanded_elements_list.remove(ifc_definition_id)
should_expand = False
else:
expanded_elements_list.append(ifc_definition_id)
should_expand = True
if is_recursive:
queue = [tool.Ifc.get().by_id(ifc_definition_id)]
while queue:
element = queue.pop()
children = [
e
for e in ifcopenshell.util.element.get_decomposition(element, is_recursive=False)
if not e.is_a("IfcFeatureElement")
]
ifc_definition_id = element.id()
if children:
if should_expand is False and ifc_definition_id in expanded_elements_list:
expanded_elements_list.remove(ifc_definition_id)
elif should_expand is True and ifc_definition_id not in expanded_elements_list:
expanded_elements_list.append(ifc_definition_id)
queue.extend(children)
props.expanded_elements = json.dumps(expanded_elements)
# HERE STARTS SPATIAL TOOL
@classmethod