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() tool.Spatial.load_contained_elements()
def update_element_mode(self, context):
tool.Spatial.load_contained_elements()
def update_container_obj(self, context): def update_container_obj(self, context):
if self.container_obj is None or not (obj := context.active_object): if self.container_obj is None or not (obj := context.active_object):
return return
@@ -112,6 +116,8 @@ class Element(PropertyGroup):
name="IFC Definition ID", 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", 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") total: IntProperty(name="Total")
is_expanded: BoolProperty(name="Is Expanded", default=False) is_expanded: BoolProperty(name="Is Expanded", default=False)
type: EnumProperty( type: EnumProperty(
@@ -134,6 +140,14 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
expanded_elements: StringProperty(name="Expanded Elements", 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")
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") subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class")
default_container: IntProperty(name="Default Container", default=0) default_container: IntProperty(name="Default Container", default=0)
should_include_children: BoolProperty( should_include_children: BoolProperty(
+17 -8
View File
@@ -160,8 +160,11 @@ class BIM_PT_spatial_decomposition(Panel):
if not self.props.total_elements: if not self.props.total_elements:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=f"{self.props.active_container.ifc_class} > No Elements", icon="FILE_3D") 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.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 return
row = self.layout.row(align=True) 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", text=f"{self.props.active_container.ifc_class} > {self.props.total_elements} Elements",
icon="FILE_3D", 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.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").container = ifc_definition_id
op = row.operator("bim.select_decomposed_element", icon="OBJECT_DATA", text="") op = row.operator("bim.select_decomposed_element", icon="OBJECT_DATA", text="")
if active_element := self.props.active_element: 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 = row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="")
op.container = ifc_definition_id 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( self.layout.template_list(
"BIM_UL_elements", "BIM_UL_elements",
"", "",
@@ -273,23 +279,26 @@ class BIM_UL_elements(UIList):
if item: if item:
row = layout.row(align=True) row = layout.row(align=True)
item_type = item.type 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) self.draw_toggle(row, item.is_expanded, index)
if item_type == "CLASS":
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_type == "TYPE": elif item_type == "TYPE":
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 elif item_type == "OCCURRENCE":
for _ in range(2):
row.label(text="", icon="BLANK1")
row.label(text=item.name) 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): def draw_filter(self, context, layout):
row = layout.row() row = layout.row()
+81
View File
@@ -226,6 +226,14 @@ class Spatial(bonsai.core.tool.Spatial):
return return
container = tool.Ifc.get().by_id(container.ifc_definition_id) 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) results = cls.get_grouped_elements_in_container(container)
expanded_elements = json.loads(props.expanded_elements) expanded_elements = json.loads(props.expanded_elements)
@@ -239,6 +247,7 @@ class Spatial(bonsai.core.tool.Spatial):
new = props.elements.add() new = props.elements.add()
new.name = ifc_class new.name = ifc_class
new.type = "CLASS" new.type = "CLASS"
new.has_children = True
class_is_expanded = ifc_class in expanded_classes class_is_expanded = ifc_class in expanded_classes
class_is_expanded_r = ifc_class in expanded_classes_r class_is_expanded_r = ifc_class in expanded_classes_r
new.is_expanded = class_is_expanded or class_is_expanded_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: if new.is_expanded:
new2 = props.elements.add() new2 = props.elements.add()
new2.type = "TYPE" new2.type = "TYPE"
new2.has_children = True
new2.level = 1
new2.name = type_data["type_name"] new2.name = type_data["type_name"]
new2.ifc_class = ifc_class new2.ifc_class = ifc_class
new2.total = total2 new2.total = total2
@@ -265,6 +276,7 @@ class Spatial(bonsai.core.tool.Spatial):
for element in type_data["elements"]: for element in type_data["elements"]:
occurrence = props.elements.add() occurrence = props.elements.add()
occurrence.name = element.Name or "Unnamed" occurrence.name = element.Name or "Unnamed"
occurrence.level = 2
occurrence.ifc_definition_id = element.id() occurrence.ifc_definition_id = element.id()
occurrence.type = "OCCURRENCE" occurrence.type = "OCCURRENCE"
@@ -274,6 +286,35 @@ class Spatial(bonsai.core.tool.Spatial):
props.total_elements = total_elements 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 @classmethod
def filter_elements( def filter_elements(
cls, cls,
@@ -373,7 +414,14 @@ class Spatial(bonsai.core.tool.Spatial):
@classmethod @classmethod
def toggle_container_element(cls, element_index: int, is_recursive: bool) -> None: def toggle_container_element(cls, element_index: int, is_recursive: bool) -> None:
props = bpy.context.scene.BIMSpatialDecompositionProperties 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) expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
element = props.elements[element_index] element = props.elements[element_index]
@@ -420,6 +468,39 @@ class Spatial(bonsai.core.tool.Spatial):
props.expanded_elements = json.dumps(expanded_elements) 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 # HERE STARTS SPATIAL TOOL
@classmethod @classmethod